diff --git a/web/app/components/datasets/external-knowledge-base/create/__tests__/index.spec.tsx b/web/app/components/datasets/external-knowledge-base/create/__tests__/index.spec.tsx index e58f1796953..d92bf20da36 100644 --- a/web/app/components/datasets/external-knowledge-base/create/__tests__/index.spec.tsx +++ b/web/app/components/datasets/external-knowledge-base/create/__tests__/index.spec.tsx @@ -17,7 +17,7 @@ vi.mock('@/next/navigation', () => ({ // Mock useDocLink hook vi.mock('@/context/i18n', () => ({ - useDocLink: () => (path?: string) => `https://docs.dify.ai/en${path || ''}`, + useDocLink: () => (path?: string) => `https://docs.dify.ai/en${path?.startsWith('/use-dify/') ? `/cloud${path}` : path || ''}`, })) // Mock external context providers (these are external dependencies) @@ -155,7 +155,7 @@ describe('ExternalKnowledgeBaseCreate', () => { renderComponent() const docLink = screen.getByText('dataset.connectHelper.helper4') - expect(docLink)!.toHaveAttribute('href', 'https://docs.dify.ai/en/use-dify/knowledge/connect-external-knowledge-base') + expect(docLink)!.toHaveAttribute('href', 'https://docs.dify.ai/en/cloud/use-dify/knowledge/connect-external-knowledge-base') expect(docLink)!.toHaveAttribute('target', '_blank') expect(docLink)!.toHaveAttribute('rel', 'noopener noreferrer') }) diff --git a/web/app/components/goto-anything/actions/commands/__tests__/direct-commands.spec.ts b/web/app/components/goto-anything/actions/commands/__tests__/direct-commands.spec.ts index f14098e970f..205d93d4124 100644 --- a/web/app/components/goto-anything/actions/commands/__tests__/direct-commands.spec.ts +++ b/web/app/components/goto-anything/actions/commands/__tests__/direct-commands.spec.ts @@ -22,6 +22,7 @@ vi.mock('react-i18next', () => ({ vi.mock('@/context/i18n', () => ({ defaultDocBaseUrl: 'https://docs.dify.ai', + getDocHomePath: () => '/home', })) vi.mock('@/i18n-config/language', () => ({ @@ -45,7 +46,7 @@ describe('docsCommand', () => { docsCommand.execute?.() expect(openSpy).toHaveBeenCalledWith( - expect.stringContaining('https://docs.dify.ai'), + 'https://docs.dify.ai/en/home', '_blank', 'noopener,noreferrer', ) @@ -85,7 +86,11 @@ describe('docsCommand', () => { const handlers = vi.mocked(registerCommands).mock.calls[0]![0] await handlers['navigation.doc']!() - expect(openSpy).toHaveBeenCalledWith('https://docs.dify.ai/en', '_blank', 'noopener,noreferrer') + expect(openSpy).toHaveBeenCalledWith( + 'https://docs.dify.ai/en/home', + '_blank', + 'noopener,noreferrer', + ) openSpy.mockRestore() }) diff --git a/web/app/components/goto-anything/actions/commands/docs.tsx b/web/app/components/goto-anything/actions/commands/docs.tsx index 6de7a7f9797..f56e0579aff 100644 --- a/web/app/components/goto-anything/actions/commands/docs.tsx +++ b/web/app/components/goto-anything/actions/commands/docs.tsx @@ -2,13 +2,20 @@ import type { SlashCommandHandler } from './types' import { RiBookOpenLine } from '@remixicon/react' import * as React from 'react' import { getI18n } from 'react-i18next' -import { defaultDocBaseUrl } from '@/context/i18n' +import { defaultDocBaseUrl, getDocHomePath } from '@/context/i18n' import { getDocLanguage } from '@/i18n-config/language' import { registerCommands, unregisterCommands } from './command-bus' // Documentation command dependency types - no external dependencies needed type DocDeps = Record +const getDocsHomeUrl = () => { + const i18n = getI18n() + const currentLocale = i18n.language + const docLanguage = getDocLanguage(currentLocale) + return `${defaultDocBaseUrl}/${docLanguage}${getDocHomePath()}` +} + /** * Documentation command - Opens help documentation */ @@ -19,11 +26,7 @@ export const docsCommand: SlashCommandHandler = { // Direct execution function execute: () => { - const i18n = getI18n() - const currentLocale = i18n.language - const docLanguage = getDocLanguage(currentLocale) - const url = `${defaultDocBaseUrl}/${docLanguage}` - window.open(url, '_blank', 'noopener,noreferrer') + window.open(getDocsHomeUrl(), '_blank', 'noopener,noreferrer') }, async search(args: string, locale: string = 'en') { @@ -43,14 +46,9 @@ export const docsCommand: SlashCommandHandler = { }, register(_deps: DocDeps) { - const i18n = getI18n() registerCommands({ 'navigation.doc': async (_args) => { - // Get the current language from i18n - const currentLocale = i18n.language - const docLanguage = getDocLanguage(currentLocale) - const url = `${defaultDocBaseUrl}/${docLanguage}` - window.open(url, '_blank', 'noopener,noreferrer') + window.open(getDocsHomeUrl(), '_blank', 'noopener,noreferrer') }, }) }, diff --git a/web/app/components/header/account-setting/api-based-extension-page/__tests__/empty.spec.tsx b/web/app/components/header/account-setting/api-based-extension-page/__tests__/empty.spec.tsx index dbbd2dc07eb..5a7edff09bd 100644 --- a/web/app/components/header/account-setting/api-based-extension-page/__tests__/empty.spec.tsx +++ b/web/app/components/header/account-setting/api-based-extension-page/__tests__/empty.spec.tsx @@ -11,8 +11,8 @@ describe('Empty State', () => { expect(screen.getByText('common.apiBasedExtension.title')).toBeInTheDocument() const link = screen.getByText('common.apiBasedExtension.link') expect(link).toBeInTheDocument() - // The real useDocLink includes the language prefix (defaulting to /en in tests) - expect(link.closest('a')).toHaveAttribute('href', 'https://docs.dify.ai/en/use-dify/workspace/api-extension/api-extension') + // The real useDocLink includes language and product prefixes in tests. + expect(link.closest('a')).toHaveAttribute('href', 'https://docs.dify.ai/en/self-host/use-dify/workspace/api-extension/api-extension') }) }) }) diff --git a/web/app/components/integrations/__tests__/page.spec.tsx b/web/app/components/integrations/__tests__/page.spec.tsx index 13c1f89bc16..18ab1a80403 100644 --- a/web/app/components/integrations/__tests__/page.spec.tsx +++ b/web/app/components/integrations/__tests__/page.spec.tsx @@ -578,7 +578,7 @@ describe('IntegrationsPage', () => { expect(screen.getAllByText('common.toolsPage.toolPlugin')).toHaveLength(2) expect(screen.getByText('common.toolsPage.description')).toBeInTheDocument() expect(screen.getByText('common.toolsPage.description').closest('[class*="max-w-[1600px]"]')).toHaveClass('px-6') - expect(screen.getByRole('link', { name: /common\.modelProvider\.learnMore/i })).toHaveAttribute('href', 'https://docs.dify.ai/en/use-dify/workspace/tools') + expect(screen.getByRole('link', { name: /common\.modelProvider\.learnMore/i })).toHaveAttribute('href', 'https://docs.dify.ai/en/self-host/use-dify/workspace/tools') }) it('aligns model provider headers to the unified content frame', () => { @@ -600,7 +600,7 @@ describe('IntegrationsPage', () => { expect(screen.getAllByText('MCP')).toHaveLength(2) expect(screen.getByText('common.mcpPage.description')).toBeInTheDocument() - expect(screen.getByRole('link', { name: /common\.modelProvider\.learnMore/i })).toHaveAttribute('href', 'https://docs.dify.ai/en/use-dify/build/mcp') + expect(screen.getByRole('link', { name: /common\.modelProvider\.learnMore/i })).toHaveAttribute('href', 'https://docs.dify.ai/en/self-host/use-dify/build/mcp') expect(screen.queryByText('common.toolsPage.description')).not.toBeInTheDocument() }) @@ -609,7 +609,7 @@ describe('IntegrationsPage', () => { expect(screen.getAllByText('common.settings.swaggerAPIAsTool')).toHaveLength(2) expect(screen.getByText('common.swaggerAPIAsToolPage.description')).toBeInTheDocument() - expect(screen.getByRole('link', { name: /common\.modelProvider\.learnMore/i })).toHaveAttribute('href', 'https://docs.dify.ai/en/use-dify/workspace/tools#custom-tool') + expect(screen.getByRole('link', { name: /common\.modelProvider\.learnMore/i })).toHaveAttribute('href', 'https://docs.dify.ai/en/self-host/use-dify/workspace/tools#custom-tool') expect(screen.queryByText('common.toolsPage.description')).not.toBeInTheDocument() }) @@ -630,7 +630,7 @@ describe('IntegrationsPage', () => { expect(screen.getAllByText('common.settings.customEndpoint')).toHaveLength(2) expect(screen.getByText('common.apiBasedExtensionPage.description')).toBeInTheDocument() expect(screen.getByTestId('api-extension-toolbar')).toBeInTheDocument() - expect(screen.getByRole('link', { name: /common\.modelProvider\.learnMore/i })).toHaveAttribute('href', 'https://docs.dify.ai/en/use-dify/workspace/api-extension/api-extension') + expect(screen.getByRole('link', { name: /common\.modelProvider\.learnMore/i })).toHaveAttribute('href', 'https://docs.dify.ai/en/self-host/use-dify/workspace/api-extension/api-extension') expect(screen.queryByText('common.toolsPage.description')).not.toBeInTheDocument() }) @@ -652,7 +652,7 @@ describe('IntegrationsPage', () => { expect(screen.getAllByText('workflow.common.workflowAsTool')).toHaveLength(2) expect(screen.getByText('common.workflowAsToolPage.description')).toBeInTheDocument() - expect(screen.getByRole('link', { name: /common\.modelProvider\.learnMore/i })).toHaveAttribute('href', 'https://docs.dify.ai/en/use-dify/workspace/tools#workflow-tool') + expect(screen.getByRole('link', { name: /common\.modelProvider\.learnMore/i })).toHaveAttribute('href', 'https://docs.dify.ai/en/self-host/use-dify/workspace/tools#workflow-tool') expect(screen.queryByText('common.toolsPage.description')).not.toBeInTheDocument() }) diff --git a/web/app/components/tools/provider/__tests__/custom-create-card.spec.tsx b/web/app/components/tools/provider/__tests__/custom-create-card.spec.tsx index c15ddb95550..0092eebdd6c 100644 --- a/web/app/components/tools/provider/__tests__/custom-create-card.spec.tsx +++ b/web/app/components/tools/provider/__tests__/custom-create-card.spec.tsx @@ -15,7 +15,7 @@ vi.mock('@/context/app-context', () => ({ // Mock useLocale and useDocLink vi.mock('@/context/i18n', () => ({ useLocale: () => 'en-US', - useDocLink: () => (path: string) => `https://docs.dify.ai/en/${path?.startsWith('/') ? path.slice(1) : path}`, + useDocLink: () => (path?: string) => `https://docs.dify.ai/en${path?.startsWith('/use-dify/') ? `/cloud${path}` : path || ''}`, })) // Mock getLanguage @@ -132,7 +132,7 @@ describe('CustomCreateCard', () => { render() const docLink = screen.getByText('tools.swaggerAPIAsToolTip').closest('a') - expect(docLink).toHaveAttribute('href', 'https://docs.dify.ai/en/use-dify/workspace/tools#custom-tool') + expect(docLink).toHaveAttribute('href', 'https://docs.dify.ai/en/cloud/use-dify/workspace/tools#custom-tool') expect(docLink).toHaveAttribute('target', '_blank') expect(docLink).toHaveAttribute('rel', 'noopener noreferrer') }) diff --git a/web/app/components/tools/provider/__tests__/empty.spec.tsx b/web/app/components/tools/provider/__tests__/empty.spec.tsx index 20738bec0f2..55dbf965a71 100644 --- a/web/app/components/tools/provider/__tests__/empty.spec.tsx +++ b/web/app/components/tools/provider/__tests__/empty.spec.tsx @@ -84,7 +84,7 @@ describe('Empty', () => { expect(screen.getByRole('link', { name: /tools\.workflowToolEmpty\.goToStudio/i })).toHaveAttribute('href', '/apps') expect(screen.getByRole('link', { name: /tools\.workflowToolEmpty\.learnMore/i })).toHaveAttribute('target', '_blank') - expect(screen.getByRole('link', { name: /tools\.workflowToolEmpty\.learnMore/i })).toHaveAttribute('href', 'https://docs.dify.ai/en/use-dify/workspace/tools#workflow-tool') + expect(screen.getByRole('link', { name: /tools\.workflowToolEmpty\.learnMore/i })).toHaveAttribute('href', 'https://docs.dify.ai/en/self-host/use-dify/workspace/tools#workflow-tool') }) }) diff --git a/web/app/components/workflow-app/hooks/__tests__/use-available-nodes-meta-data.spec.ts b/web/app/components/workflow-app/hooks/__tests__/use-available-nodes-meta-data.spec.ts index 17083cc87b9..4afcbd07387 100644 --- a/web/app/components/workflow-app/hooks/__tests__/use-available-nodes-meta-data.spec.ts +++ b/web/app/components/workflow-app/hooks/__tests__/use-available-nodes-meta-data.spec.ts @@ -54,6 +54,18 @@ describe('useAvailableNodesMetaData', () => { }) }) + it('should use explicit docs pages and skip nodes without generated docs pages', () => { + mockUseIsChatMode.mockReturnValue(false) + + const { result } = renderHook(() => useAvailableNodesMetaData()) + + expect(result.current.nodesMap?.[BlockEnum.End]?.metaData.helpLinkUri).toBe('/docs/use-dify/nodes/output') + expect(result.current.nodesMap?.[BlockEnum.IterationStart]?.metaData.helpLinkUri).toBeUndefined() + expect(result.current.nodesMap?.[BlockEnum.LoopStart]?.metaData.helpLinkUri).toBeUndefined() + expect(result.current.nodesMap?.[BlockEnum.LoopEnd]?.metaData.helpLinkUri).toBeUndefined() + expect(result.current.nodesMap?.[BlockEnum.Start]?.metaData.helpLinkUri).toBe('/docs/use-dify/nodes/user-input') + }) + it('should expose Agent v2 instead of legacy Agent when Agent v2 is enabled', () => { mockUseIsChatMode.mockReturnValue(false) diff --git a/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts b/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts index b001c381898..798181df3c7 100644 --- a/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts +++ b/web/app/components/workflow-app/hooks/use-available-nodes-meta-data.ts @@ -14,8 +14,20 @@ import TriggerWebhookDefault from '@/app/components/workflow/nodes/trigger-webho import { BlockEnum } from '@/app/components/workflow/types' import { useDocLink } from '@/context/i18n' import { isAgentV2Enabled } from '@/features/agent-v2/feature-flag' +import { docPathProductAvailability } from '@/types/doc-paths' import { useIsChatMode } from './use-is-chat-mode' +const getNodeHelpLinkPath = (helpLinkUri?: string): DocPathWithoutLang | undefined => { + if (!helpLinkUri) + return undefined + + const helpLinkPath = `/use-dify/nodes/${helpLinkUri}` + if (!docPathProductAvailability[helpLinkPath]) + return undefined + + return helpLinkPath as DocPathWithoutLang +} + export const useAvailableNodesMetaData = () => { const { t } = useTranslation() const isChatMode = useIsChatMode() @@ -57,14 +69,14 @@ export const useAvailableNodesMetaData = () => { const { metaData } = node const title = t(`blocks.${metaData.type}`, { ns: 'workflow' }) const description = t(`blocksAbout.${metaData.type}` as I18nKeysWithPrefix<'workflow', 'blocksAbout.'>, { ns: 'workflow' }) - const helpLinkPath = `/use-dify/nodes/${metaData.helpLinkUri}` as DocPathWithoutLang + const helpLinkPath = getNodeHelpLinkPath(metaData.helpLinkUri) return { ...node, metaData: { ...metaData, title, description, - helpLinkUri: docLink(helpLinkPath), + helpLinkUri: helpLinkPath ? docLink(helpLinkPath) : undefined, }, defaultValue: { ...node.defaultValue, diff --git a/web/app/components/workflow/block-selector/__tests__/main.spec.tsx b/web/app/components/workflow/block-selector/__tests__/main.spec.tsx index fb56beda545..b7248dcb90a 100644 --- a/web/app/components/workflow/block-selector/__tests__/main.spec.tsx +++ b/web/app/components/workflow/block-selector/__tests__/main.spec.tsx @@ -318,7 +318,7 @@ describe('NodeSelector', () => { expect(await screen.findByText('workflow.tabs.unconfiguredStartDisabledTip')).toBeInTheDocument() expect(screen.getByRole('link', { name: 'workflow.tabs.startDisabledTipLearnMore' })).toHaveAttribute( 'href', - 'https://docs.dify.ai/en/use-dify/nodes/trigger/overview', + 'https://docs.dify.ai/en/self-host/use-dify/nodes/trigger/overview', ) expect(screen.getByPlaceholderText('workflow.tabs.searchBlock')).toBeInTheDocument() }) diff --git a/web/app/components/workflow/nodes/end/default.ts b/web/app/components/workflow/nodes/end/default.ts index 146ffc6797b..98dcbc2637d 100644 --- a/web/app/components/workflow/nodes/end/default.ts +++ b/web/app/components/workflow/nodes/end/default.ts @@ -6,6 +6,7 @@ import { genNodeMetaData } from '@/app/components/workflow/utils' const metaData = genNodeMetaData({ sort: 2.1, type: BlockEnum.End, + helpLinkUri: 'output', isRequired: false, }) const nodeDefault: NodeDefault = { diff --git a/web/context/i18n.spec.ts b/web/context/i18n.spec.ts index 83e510f040c..0fc0ae5c809 100644 --- a/web/context/i18n.spec.ts +++ b/web/context/i18n.spec.ts @@ -5,6 +5,10 @@ import { useTranslation } from '#i18n' import { getDocLanguage } from '@/i18n-config/language' import { defaultDocBaseUrl, useDocLink } from './i18n' +const mockConfig = vi.hoisted(() => ({ + IS_CLOUD_EDITION: true, +})) + // Mock dependencies vi.mock('#i18n', () => ({ useTranslation: vi.fn(() => ({ @@ -12,6 +16,12 @@ vi.mock('#i18n', () => ({ })), })) +vi.mock('@/config', () => ({ + get IS_CLOUD_EDITION() { + return mockConfig.IS_CLOUD_EDITION + }, +})) + vi.mock('@/i18n-config/language', () => ({ getDocLanguage: vi.fn((locale: string) => { const map: Record = { @@ -28,6 +38,7 @@ vi.mock('@/i18n-config/language', () => ({ describe('useDocLink', () => { beforeEach(() => { vi.clearAllMocks() + mockConfig.IS_CLOUD_EDITION = true vi.mocked(useTranslation).mockReturnValue({ i18n: { language: 'en-US' }, } as ReturnType) @@ -45,28 +56,28 @@ describe('useDocLink', () => { it('should use default base URL when no baseUrl provided', () => { const { result } = renderHook(() => useDocLink()) const url = result.current() - expect(url).toBe(`${defaultDocBaseUrl}/en`) + expect(url).toBe(`${defaultDocBaseUrl}/en/home`) }) it('should use custom base URL when provided', () => { const customBaseUrl = 'https://custom.docs.com' const { result } = renderHook(() => useDocLink(customBaseUrl)) const url = result.current() - expect(url).toBe(`${customBaseUrl}/en`) + expect(url).toBe(`${customBaseUrl}/en/home`) }) it('should remove trailing slash from base URL', () => { const baseUrlWithSlash = 'https://docs.dify.ai/' const { result } = renderHook(() => useDocLink(baseUrlWithSlash)) const url = result.current('/use-dify/getting-started/introduction') - expect(url).toBe('https://docs.dify.ai/en/use-dify/getting-started/introduction') + expect(url).toBe('https://docs.dify.ai/en/cloud/use-dify/getting-started/introduction') }) it('should handle base URL without trailing slash', () => { const baseUrlWithoutSlash = 'https://docs.dify.ai' const { result } = renderHook(() => useDocLink(baseUrlWithoutSlash)) const url = result.current('/use-dify/getting-started/introduction') - expect(url).toBe('https://docs.dify.ai/en/use-dify/getting-started/introduction') + expect(url).toBe('https://docs.dify.ai/en/cloud/use-dify/getting-started/introduction') }) }) @@ -74,19 +85,31 @@ describe('useDocLink', () => { it('should handle path parameter', () => { const { result } = renderHook(() => useDocLink()) const url = result.current('/use-dify/getting-started/introduction') - expect(url).toBe(`${defaultDocBaseUrl}/en/use-dify/getting-started/introduction`) + expect(url).toBe(`${defaultDocBaseUrl}/en/cloud/use-dify/getting-started/introduction`) }) it('should handle empty path', () => { const { result } = renderHook(() => useDocLink()) const url = result.current() - expect(url).toBe(`${defaultDocBaseUrl}/en`) + expect(url).toBe(`${defaultDocBaseUrl}/en/home`) }) it('should handle undefined path', () => { const { result } = renderHook(() => useDocLink()) const url = result.current(undefined) - expect(url).toBe(`${defaultDocBaseUrl}/en`) + expect(url).toBe(`${defaultDocBaseUrl}/en/home`) + }) + + it('should keep common docs path without product prefix', () => { + const { result } = renderHook(() => useDocLink()) + const url = result.current('/learn/key-concepts' as DocPathWithoutLang) + expect(url).toBe(`${defaultDocBaseUrl}/en/learn/key-concepts`) + }) + + it('should keep explicit product docs path without adding another product prefix', () => { + const { result } = renderHook(() => useDocLink()) + const url = result.current('/cloud/use-dify/build/mcp' as DocPathWithoutLang) + expect(url).toBe(`${defaultDocBaseUrl}/en/cloud/use-dify/build/mcp`) }) }) @@ -99,12 +122,12 @@ describe('useDocLink', () => { const pathMap: DocPathMap = { 'zh-Hans': '/use-dify/getting-started/introduction', - 'en-US': '/use-dify/getting-started/quick-start', + 'en-US': '/use-dify/build/mcp', } const { result } = renderHook(() => useDocLink()) - const url = result.current('/use-dify/getting-started/quick-start' as DocPathWithoutLang, pathMap) - expect(url).toBe(`${defaultDocBaseUrl}/zh/use-dify/getting-started/introduction`) + const url = result.current('/use-dify/build/mcp', pathMap) + expect(url).toBe(`${defaultDocBaseUrl}/zh/cloud/use-dify/getting-started/introduction`) }) it('should use default path when locale not in pathMap', () => { @@ -115,18 +138,76 @@ describe('useDocLink', () => { const pathMap: DocPathMap = { 'zh-Hans': '/use-dify/getting-started/introduction', - 'en-US': '/use-dify/getting-started/quick-start', + 'en-US': '/use-dify/build/mcp', } const { result } = renderHook(() => useDocLink()) - const url = result.current('/use-dify/getting-started/quick-start' as DocPathWithoutLang, pathMap) - expect(url).toBe(`${defaultDocBaseUrl}/ja/use-dify/getting-started/quick-start`) + const url = result.current('/use-dify/build/mcp', pathMap) + expect(url).toBe(`${defaultDocBaseUrl}/ja/cloud/use-dify/build/mcp`) }) it('should handle undefined pathMap', () => { const { result } = renderHook(() => useDocLink()) const url = result.current('/use-dify/getting-started/introduction', undefined) - expect(url).toBe(`${defaultDocBaseUrl}/en/use-dify/getting-started/introduction`) + expect(url).toBe(`${defaultDocBaseUrl}/en/cloud/use-dify/getting-started/introduction`) + }) + }) + + describe('Product prefix handling', () => { + it('should add cloud product prefix for product docs available in both editions', () => { + mockConfig.IS_CLOUD_EDITION = true + + const { result } = renderHook(() => useDocLink()) + const url = result.current('/use-dify/build/mcp') + expect(url).toBe(`${defaultDocBaseUrl}/en/cloud/use-dify/build/mcp`) + }) + + it('should add self-host product prefix for product docs available in both editions outside cloud edition', () => { + mockConfig.IS_CLOUD_EDITION = false + + const { result } = renderHook(() => useDocLink()) + const url = result.current('/use-dify/build/mcp') + expect(url).toBe(`${defaultDocBaseUrl}/en/self-host/use-dify/build/mcp`) + }) + + it('should use the existing cloud docs path for cloud-only product docs outside cloud edition', () => { + mockConfig.IS_CLOUD_EDITION = false + + const { result } = renderHook(() => useDocLink()) + const url = result.current('/use-dify/workspace/subscription-management#dify-for-education') + expect(url).toBe(`${defaultDocBaseUrl}/en/cloud/use-dify/workspace/subscription-management#dify-for-education`) + }) + + it('should use the existing self-host docs path for self-host-only product docs in cloud edition', () => { + mockConfig.IS_CLOUD_EDITION = true + + const { result } = renderHook(() => useDocLink()) + const url = result.current('/deploy/overview') + expect(url).toBe(`${defaultDocBaseUrl}/en/self-host/deploy/overview`) + }) + + it('should not add a product prefix for unknown productless paths', () => { + mockConfig.IS_CLOUD_EDITION = false + + const { result } = renderHook(() => useDocLink()) + const url = result.current('/use-dify/unknown-page' as DocPathWithoutLang) + expect(url).toBe(`${defaultDocBaseUrl}/en/use-dify/unknown-page`) + }) + + it('should open shared docs home when no path is provided outside cloud edition', () => { + mockConfig.IS_CLOUD_EDITION = false + + const { result } = renderHook(() => useDocLink()) + const url = result.current() + expect(url).toBe(`${defaultDocBaseUrl}/en/home`) + }) + + it('should keep self-host deploy paths without adding use-dify product prefix', () => { + mockConfig.IS_CLOUD_EDITION = true + + const { result } = renderHook(() => useDocLink()) + const url = result.current('/self-host/deploy/overview' as DocPathWithoutLang) + expect(url).toBe(`${defaultDocBaseUrl}/en/self-host/deploy/overview`) }) }) @@ -232,7 +313,7 @@ describe('useDocLink', () => { const { result } = renderHook(() => useDocLink()) const url = result.current('/use-dify/getting-started/introduction') - expect(url).toBe(`${defaultDocBaseUrl}/zh/use-dify/getting-started/introduction`) + expect(url).toBe(`${defaultDocBaseUrl}/zh/cloud/use-dify/getting-started/introduction`) }) }) @@ -240,15 +321,15 @@ describe('useDocLink', () => { it('should handle path with anchor', () => { const { result } = renderHook(() => useDocLink()) const url = result.current('/use-dify/getting-started/introduction#overview' as DocPathWithoutLang) - expect(url).toBe(`${defaultDocBaseUrl}/en/use-dify/getting-started/introduction#overview`) + expect(url).toBe(`${defaultDocBaseUrl}/en/cloud/use-dify/getting-started/introduction#overview`) }) it('should handle multiple calls with same hook instance', () => { const { result } = renderHook(() => useDocLink()) const url1 = result.current('/use-dify/getting-started/introduction') - const url2 = result.current('/use-dify/getting-started/quick-start') - expect(url1).toBe(`${defaultDocBaseUrl}/en/use-dify/getting-started/introduction`) - expect(url2).toBe(`${defaultDocBaseUrl}/en/use-dify/getting-started/quick-start`) + const url2 = result.current('/use-dify/build/mcp') + expect(url1).toBe(`${defaultDocBaseUrl}/en/cloud/use-dify/getting-started/introduction`) + expect(url2).toBe(`${defaultDocBaseUrl}/en/cloud/use-dify/build/mcp`) }) }) }) diff --git a/web/context/i18n.ts b/web/context/i18n.ts index ac11f44f323..a8f7dfa5959 100644 --- a/web/context/i18n.ts +++ b/web/context/i18n.ts @@ -1,9 +1,10 @@ import type { Locale } from '@/i18n-config/language' -import type { DocPathWithoutLang } from '@/types/doc-paths' +import type { DocPathWithoutLang, DocsProduct } from '@/types/doc-paths' import { useCallback } from 'react' import { useTranslation } from '#i18n' +import { IS_CLOUD_EDITION } from '@/config' import { getDocLanguage, getLanguage, getPricingPageLanguage } from '@/i18n-config/language' -import { apiReferencePathTranslations } from '@/types/doc-paths' +import { apiReferencePathTranslations, docPathProductAvailability } from '@/types/doc-paths' export const useLocale = () => { const { i18n } = useTranslation() @@ -24,6 +25,44 @@ export const useGetPricingPageLanguage = () => { export const defaultDocBaseUrl = 'https://docs.dify.ai' export type DocPathMap = Partial> +export const getDocHomePath = () => '/home' + +const getCurrentDocsProduct = (): DocsProduct => { + return IS_CLOUD_EDITION ? 'cloud' : 'self-host' +} + +const splitPathHash = (path: string) => { + const hashIndex = path.indexOf('#') + if (hashIndex === -1) { + return { + pathname: path, + hash: '', + } + } + + return { + pathname: path.slice(0, hashIndex), + hash: path.slice(hashIndex), + } +} + +const getProductAwarePath = (path: string): string => { + const { pathname, hash } = splitPathHash(path) + const availableProducts = docPathProductAvailability[pathname] + if (!availableProducts?.length) + return path + + const currentProduct = getCurrentDocsProduct() + const targetProduct = availableProducts.includes(currentProduct) + ? currentProduct + : availableProducts[0] + + if (!targetProduct) + return path + + return `/${targetProduct}${pathname}${hash}` +} + export const useDocLink = (baseUrl?: string): ((path?: DocPathWithoutLang, pathMap?: DocPathMap) => string) => { let baseDocUrl = baseUrl || defaultDocBaseUrl baseDocUrl = (baseDocUrl.endsWith('/')) ? baseDocUrl.slice(0, -1) : baseDocUrl @@ -44,6 +83,12 @@ export const useDocLink = (baseUrl?: string): ((path?: DocPathWithoutLang, pathM } } } + else if (!targetPath) { + targetPath = getDocHomePath() + } + else { + targetPath = getProductAwarePath(targetPath) + } return `${baseDocUrl}${languagePrefix}${targetPath}` }, diff --git a/web/features/agent-v2/agent-detail/access/page.tsx b/web/features/agent-v2/agent-detail/access/page.tsx index 6b3e2af6a06..a22373e7bfe 100644 --- a/web/features/agent-v2/agent-detail/access/page.tsx +++ b/web/features/agent-v2/agent-detail/access/page.tsx @@ -84,7 +84,7 @@ export function AgentAccessPage({

{t('agentDetail.access.description')} > + +function isDocsProduct(segment: string): segment is DocsProduct { + return DOCS_PRODUCTS.includes(segment as DocsProduct) +} /** * Convert summary to URL slug @@ -112,6 +124,15 @@ function extractOpenAPIPaths(item: NavItem | undefined, paths: Set = new if (item.languages) extractOpenAPIPaths(item.languages, paths) + if (item.products) + extractOpenAPIPaths(item.products, paths) + + if (item.tabs) + extractOpenAPIPaths(item.tabs, paths) + + if (item.menu) + extractOpenAPIPaths(item.menu, paths) + if (item.versions) extractOpenAPIPaths(item.versions, paths) } @@ -199,6 +220,15 @@ function extractPaths(item: NavItem | undefined, paths: Set = new Set()) if (item.languages) extractPaths(item.languages, paths) + if (item.products) + extractPaths(item.products, paths) + + if (item.tabs) + extractPaths(item.tabs, paths) + + if (item.menu) + extractPaths(item.menu, paths) + // Handle versions in navigation if (item.versions) extractPaths(item.versions, paths) @@ -207,33 +237,68 @@ function extractPaths(item: NavItem | undefined, paths: Set = new Set()) return paths } +function addPathToGroup(groups: Record>, pathWithoutLang: string): void { + const parts = pathWithoutLang.split('/') + const section = parts[0] + if (!section) + return + + if (!groups[section]) + groups[section] = new Set() + + groups[section]!.add(pathWithoutLang) +} + +function getProductPathInfo(pathWithoutLang: string): { product: DocsProduct, pathWithoutProduct: string } | undefined { + const parts = pathWithoutLang.split('/') + const [product, ...rest] = parts + + if (!product || !isDocsProduct(product) || rest.length === 0) + return undefined + + return { + product, + pathWithoutProduct: rest.join('/'), + } +} + /** * Group paths by their prefix structure */ -function groupPathsBySection(paths: Set): Record> { +function groupPathsBySection(paths: Set): { groups: Record>, productAvailability: ProductAvailability } { const groups: Record> = {} + const productAvailability: ProductAvailability = {} for (const fullPath of paths) { - // Skip non-doc paths (like .json files for OpenAPI) - if (fullPath.endsWith('.json')) - continue - // Remove language prefix (en/, zh/, ja/) const withoutLang = fullPath.replace(/^(en|zh|ja)\//, '') if (!withoutLang || withoutLang === fullPath) continue - // Get section (first part of path) - const parts = withoutLang.split('/') - const section = parts[0] + // Skip non-doc paths (like .json files for OpenAPI) + if (withoutLang.endsWith('.json') || withoutLang === 'None') + continue - if (!groups[section!]) - groups[section!] = new Set() + addPathToGroup(groups, withoutLang) - groups[section!]!.add(withoutLang) + const productPathInfo = getProductPathInfo(withoutLang) + if (productPathInfo) { + const productlessPath = productPathInfo.pathWithoutProduct + const normalizedPath = `/${productlessPath}` + + addPathToGroup(groups, productlessPath) + + if (!productAvailability[normalizedPath]) + productAvailability[normalizedPath] = new Set() + + productAvailability[normalizedPath]!.add(productPathInfo.product) + } } - return groups + return { + groups, + productAvailability, + } } /** @@ -251,6 +316,7 @@ function sectionToTypeName(section: string): string { */ function generateTypeDefinitions( groups: Record>, + productAvailability: ProductAvailability, apiReferencePaths: string[], apiPathTranslations: Record, ): string { @@ -258,11 +324,12 @@ function generateTypeDefinitions( '// GENERATE BY script', '// DON NOT EDIT IT MANUALLY', '//', - '// Generated from: https://raw.githubusercontent.com/langgenius/dify-docs/refs/heads/main/docs.json', + `// Generated from: ${DOCS_JSON_URL}`, `// Generated at: ${new Date().toISOString()}`, '', '// Language prefixes', 'export type DocLanguage = \'en\' | \'zh\' | \'ja\'', + 'export type DocsProduct = \'cloud\' | \'self-host\'', '', ] @@ -321,10 +388,14 @@ function generateTypeDefinitions( lines.push(' | `${DocPathWithoutLangBase}#${string}`') lines.push('') - // Generate full path type with language prefix - lines.push('// Full documentation path with language prefix') - // eslint-disable-next-line no-template-curly-in-string - lines.push('export type DifyDocPath = `${DocLanguage}/${DocPathWithoutLang}`') + // Generate product availability map for productless runtime links. + lines.push('// Product availability for productless docs paths') + lines.push('export const docPathProductAvailability: Record = {') + for (const path of Object.keys(productAvailability).sort()) { + const products = [...productAvailability[path]!].sort((a, b) => DOCS_PRODUCTS.indexOf(a) - DOCS_PRODUCTS.indexOf(b)) + lines.push(` '${path}': [${products.map(product => `'${product}'`).join(', ')}],`) + } + lines.push('}') lines.push('') // Generate API reference path translations map @@ -423,12 +494,13 @@ async function main(): Promise { console.log(`Generated ${Object.keys(apiPathTranslations).length} API path translations`) // Group by section - const groups = groupPathsBySection(allPaths) + const { groups, productAvailability } = groupPathsBySection(allPaths) console.log(`Grouped into ${Object.keys(groups).length} sections:`, Object.keys(groups)) + console.log(`Found ${Object.keys(productAvailability).length} product-aware paths`) // Generate TypeScript - const tsContent = generateTypeDefinitions(groups, uniqueEnApiPaths, apiPathTranslations) + const tsContent = generateTypeDefinitions(groups, productAvailability, uniqueEnApiPaths, apiPathTranslations) // Write to file await writeFile(OUTPUT_PATH, tsContent, 'utf-8') diff --git a/web/types/doc-paths.ts b/web/types/doc-paths.ts index f97883d4d4f..a50afc87261 100644 --- a/web/types/doc-paths.ts +++ b/web/types/doc-paths.ts @@ -1,28 +1,127 @@ // GENERATE BY script // DON NOT EDIT IT MANUALLY // -// Generated from: https://raw.githubusercontent.com/langgenius/dify-docs/refs/heads/main/docs.json -// Generated at: 2026-03-25T03:18:49.626Z +// Generated from: https://raw.githubusercontent.com/langgenius/dify-docs/feat/audience-products/docs.json +// Generated at: 2026-06-17T04:42:51.293Z // Language prefixes export type DocLanguage = 'en' | 'zh' | 'ja' +export type DocsProduct = 'cloud' | 'self-host' + +// Cloud paths +type CloudPath = + | '/cloud/use-dify/build/additional-features' + | '/cloud/use-dify/build/agent' + | '/cloud/use-dify/build/chatbot' + | '/cloud/use-dify/build/mcp' + | '/cloud/use-dify/build/orchestrate-node' + | '/cloud/use-dify/build/predefined-error-handling-logic' + | '/cloud/use-dify/build/shortcut-key' + | '/cloud/use-dify/build/text-generator' + | '/cloud/use-dify/build/version-control' + | '/cloud/use-dify/build/workflow-chatflow' + | '/cloud/use-dify/build/workflow-collaboration' + | '/cloud/use-dify/debug/error-type' + | '/cloud/use-dify/debug/history-and-logs' + | '/cloud/use-dify/debug/step-run' + | '/cloud/use-dify/debug/variable-inspect' + | '/cloud/use-dify/getting-started/introduction' + | '/cloud/use-dify/knowledge/connect-external-knowledge-base' + | '/cloud/use-dify/knowledge/create-knowledge/chunking-and-cleaning-text' + | '/cloud/use-dify/knowledge/create-knowledge/import-text-data/readme' + | '/cloud/use-dify/knowledge/create-knowledge/import-text-data/sync-from-notion' + | '/cloud/use-dify/knowledge/create-knowledge/import-text-data/sync-from-website' + | '/cloud/use-dify/knowledge/create-knowledge/introduction' + | '/cloud/use-dify/knowledge/create-knowledge/setting-indexing-methods' + | '/cloud/use-dify/knowledge/external-knowledge-api' + | '/cloud/use-dify/knowledge/integrate-knowledge-within-application' + | '/cloud/use-dify/knowledge/knowledge-pipeline/authorize-data-source' + | '/cloud/use-dify/knowledge/knowledge-pipeline/create-knowledge-pipeline' + | '/cloud/use-dify/knowledge/knowledge-pipeline/knowledge-pipeline-orchestration' + | '/cloud/use-dify/knowledge/knowledge-pipeline/manage-knowledge-base' + | '/cloud/use-dify/knowledge/knowledge-pipeline/publish-knowledge-pipeline' + | '/cloud/use-dify/knowledge/knowledge-pipeline/readme' + | '/cloud/use-dify/knowledge/knowledge-pipeline/upload-files' + | '/cloud/use-dify/knowledge/knowledge-request-rate-limit' + | '/cloud/use-dify/knowledge/manage-knowledge/introduction' + | '/cloud/use-dify/knowledge/manage-knowledge/maintain-dataset-via-api' + | '/cloud/use-dify/knowledge/manage-knowledge/maintain-knowledge-documents' + | '/cloud/use-dify/knowledge/metadata' + | '/cloud/use-dify/knowledge/readme' + | '/cloud/use-dify/knowledge/test-retrieval' + | '/cloud/use-dify/monitor/analysis' + | '/cloud/use-dify/monitor/annotation-reply' + | '/cloud/use-dify/monitor/integrations/integrate-aliyun' + | '/cloud/use-dify/monitor/integrations/integrate-arize' + | '/cloud/use-dify/monitor/integrations/integrate-langfuse' + | '/cloud/use-dify/monitor/integrations/integrate-langsmith' + | '/cloud/use-dify/monitor/integrations/integrate-opik' + | '/cloud/use-dify/monitor/integrations/integrate-phoenix' + | '/cloud/use-dify/monitor/integrations/integrate-weave' + | '/cloud/use-dify/monitor/logs' + | '/cloud/use-dify/nodes/agent' + | '/cloud/use-dify/nodes/answer' + | '/cloud/use-dify/nodes/code' + | '/cloud/use-dify/nodes/doc-extractor' + | '/cloud/use-dify/nodes/http-request' + | '/cloud/use-dify/nodes/human-input' + | '/cloud/use-dify/nodes/ifelse' + | '/cloud/use-dify/nodes/iteration' + | '/cloud/use-dify/nodes/knowledge-retrieval' + | '/cloud/use-dify/nodes/list-operator' + | '/cloud/use-dify/nodes/llm' + | '/cloud/use-dify/nodes/loop' + | '/cloud/use-dify/nodes/output' + | '/cloud/use-dify/nodes/parameter-extractor' + | '/cloud/use-dify/nodes/question-classifier' + | '/cloud/use-dify/nodes/template' + | '/cloud/use-dify/nodes/tools' + | '/cloud/use-dify/nodes/trigger/overview' + | '/cloud/use-dify/nodes/trigger/plugin-trigger' + | '/cloud/use-dify/nodes/trigger/schedule-trigger' + | '/cloud/use-dify/nodes/trigger/webhook-trigger' + | '/cloud/use-dify/nodes/user-input' + | '/cloud/use-dify/nodes/variable-aggregator' + | '/cloud/use-dify/nodes/variable-assigner' + | '/cloud/use-dify/publish/README' + | '/cloud/use-dify/publish/developing-with-apis' + | '/cloud/use-dify/publish/publish-mcp' + | '/cloud/use-dify/publish/publish-to-marketplace' + | '/cloud/use-dify/publish/webapp/chatflow-webapp' + | '/cloud/use-dify/publish/webapp/embedding-in-websites' + | '/cloud/use-dify/publish/webapp/web-app-settings' + | '/cloud/use-dify/publish/webapp/workflow-webapp' + | '/cloud/use-dify/workspace/api-extension/api-extension' + | '/cloud/use-dify/workspace/api-extension/cloudflare-worker' + | '/cloud/use-dify/workspace/api-extension/external-data-tool-api-extension' + | '/cloud/use-dify/workspace/api-extension/moderation-api-extension' + | '/cloud/use-dify/workspace/app-management' + | '/cloud/use-dify/workspace/model-providers' + | '/cloud/use-dify/workspace/personal-account-management' + | '/cloud/use-dify/workspace/plugins' + | '/cloud/use-dify/workspace/readme' + | '/cloud/use-dify/workspace/subscription-management' + | '/cloud/use-dify/workspace/team-members-management' + | '/cloud/use-dify/workspace/tools' // UseDify paths type UseDifyPath = | '/use-dify/build/additional-features' - | '/use-dify/build/goto-anything' + | '/use-dify/build/agent' + | '/use-dify/build/chatbot' | '/use-dify/build/mcp' | '/use-dify/build/orchestrate-node' | '/use-dify/build/predefined-error-handling-logic' | '/use-dify/build/shortcut-key' + | '/use-dify/build/text-generator' | '/use-dify/build/version-control' + | '/use-dify/build/workflow-chatflow' + | '/use-dify/build/workflow-collaboration' | '/use-dify/debug/error-type' | '/use-dify/debug/history-and-logs' | '/use-dify/debug/step-run' | '/use-dify/debug/variable-inspect' | '/use-dify/getting-started/introduction' - | '/use-dify/getting-started/key-concepts' - | '/use-dify/getting-started/quick-start' | '/use-dify/knowledge/connect-external-knowledge-base' | '/use-dify/knowledge/create-knowledge/chunking-and-cleaning-text' | '/use-dify/knowledge/create-knowledge/import-text-data/readme' @@ -86,24 +185,8 @@ type UseDifyPath = | '/use-dify/publish/publish-to-marketplace' | '/use-dify/publish/webapp/chatflow-webapp' | '/use-dify/publish/webapp/embedding-in-websites' - | '/use-dify/publish/webapp/web-app-access' | '/use-dify/publish/webapp/web-app-settings' | '/use-dify/publish/webapp/workflow-webapp' - | '/use-dify/tutorials/article-reader' - | '/use-dify/tutorials/build-ai-image-generation-app' - | '/use-dify/tutorials/customer-service-bot' - | '/use-dify/tutorials/simple-chatbot' - | '/use-dify/tutorials/twitter-chatflow' - | '/use-dify/tutorials/workflow-101/lesson-01' - | '/use-dify/tutorials/workflow-101/lesson-02' - | '/use-dify/tutorials/workflow-101/lesson-03' - | '/use-dify/tutorials/workflow-101/lesson-04' - | '/use-dify/tutorials/workflow-101/lesson-05' - | '/use-dify/tutorials/workflow-101/lesson-06' - | '/use-dify/tutorials/workflow-101/lesson-07' - | '/use-dify/tutorials/workflow-101/lesson-08' - | '/use-dify/tutorials/workflow-101/lesson-09' - | '/use-dify/tutorials/workflow-101/lesson-10' | '/use-dify/workspace/api-extension/api-extension' | '/use-dify/workspace/api-extension/cloudflare-worker' | '/use-dify/workspace/api-extension/external-data-tool-api-extension' @@ -115,25 +198,42 @@ type UseDifyPath = | '/use-dify/workspace/readme' | '/use-dify/workspace/subscription-management' | '/use-dify/workspace/team-members-management' + | '/use-dify/workspace/tools' // UseDify node paths (without prefix) type ExtractNodesPath = T extends `/use-dify/nodes/${infer Path}` ? Path : never export type UseDifyNodesPath = ExtractNodesPath -// SelfHost paths -type SelfHostPath = - | '/self-host/advanced-deployments/local-source-code' - | '/self-host/advanced-deployments/start-the-frontend-docker-container' - | '/self-host/configuration/environments' - | '/self-host/platform-guides/bt-panel' - | '/self-host/platform-guides/dify-premium' - | '/self-host/quick-start/docker-compose' - | '/self-host/quick-start/faqs' - | '/self-host/troubleshooting/common-issues' - | '/self-host/troubleshooting/docker-issues' - | '/self-host/troubleshooting/integrations' - | '/self-host/troubleshooting/storage-and-migration' - | '/self-host/troubleshooting/weaviate-v4-migration' +// Home paths +type HomePath = + | '/home' + +// Learn paths +type LearnPath = + | '/learn/key-concepts' + | '/learn/tutorials/article-reader' + | '/learn/tutorials/build-ai-image-generation-app' + | '/learn/tutorials/customer-service-bot' + | '/learn/tutorials/simple-chatbot' + | '/learn/tutorials/twitter-chatflow' + | '/learn/tutorials/workflow-101/lesson-01' + | '/learn/tutorials/workflow-101/lesson-02' + | '/learn/tutorials/workflow-101/lesson-03' + | '/learn/tutorials/workflow-101/lesson-04' + | '/learn/tutorials/workflow-101/lesson-05' + | '/learn/tutorials/workflow-101/lesson-06' + | '/learn/tutorials/workflow-101/lesson-07' + | '/learn/tutorials/workflow-101/lesson-08' + | '/learn/tutorials/workflow-101/lesson-09' + | '/learn/tutorials/workflow-101/lesson-10' + +// QuickStart paths +type QuickStartPath = + | '/quick-start' + +// Cli paths +type CliPath = + | '/cli/coming-soon' // DevelopPlugin paths type DevelopPluginPath = @@ -165,6 +265,7 @@ type DevelopPluginPath = | '/develop-plugin/features-and-specs/plugin-types/plugin-logging' | '/develop-plugin/features-and-specs/plugin-types/remote-debug-a-plugin' | '/develop-plugin/features-and-specs/plugin-types/tool' + | '/develop-plugin/getting-started/choose-plugin-type' | '/develop-plugin/getting-started/cli' | '/develop-plugin/getting-started/getting-started-dify-plugin' | '/develop-plugin/publishing/faq/faq' @@ -177,6 +278,129 @@ type DevelopPluginPath = | '/develop-plugin/publishing/standards/privacy-protection-guidelines' | '/develop-plugin/publishing/standards/third-party-signature-verification' +// SelfHost paths +type SelfHostPath = + | '/self-host/deploy/advanced-deployments/local-source-code' + | '/self-host/deploy/advanced-deployments/start-the-frontend-docker-container' + | '/self-host/deploy/configuration/environments' + | '/self-host/deploy/overview' + | '/self-host/deploy/platform-guides/bt-panel' + | '/self-host/deploy/platform-guides/dify-premium' + | '/self-host/deploy/quick-start/docker-compose' + | '/self-host/deploy/quick-start/faqs' + | '/self-host/deploy/troubleshooting/common-issues' + | '/self-host/deploy/troubleshooting/docker-issues' + | '/self-host/deploy/troubleshooting/integrations' + | '/self-host/deploy/troubleshooting/storage-and-migration' + | '/self-host/deploy/troubleshooting/weaviate-v4-migration' + | '/self-host/use-dify/build/additional-features' + | '/self-host/use-dify/build/agent' + | '/self-host/use-dify/build/chatbot' + | '/self-host/use-dify/build/mcp' + | '/self-host/use-dify/build/orchestrate-node' + | '/self-host/use-dify/build/predefined-error-handling-logic' + | '/self-host/use-dify/build/shortcut-key' + | '/self-host/use-dify/build/text-generator' + | '/self-host/use-dify/build/version-control' + | '/self-host/use-dify/build/workflow-chatflow' + | '/self-host/use-dify/build/workflow-collaboration' + | '/self-host/use-dify/debug/error-type' + | '/self-host/use-dify/debug/history-and-logs' + | '/self-host/use-dify/debug/step-run' + | '/self-host/use-dify/debug/variable-inspect' + | '/self-host/use-dify/getting-started/introduction' + | '/self-host/use-dify/knowledge/connect-external-knowledge-base' + | '/self-host/use-dify/knowledge/create-knowledge/chunking-and-cleaning-text' + | '/self-host/use-dify/knowledge/create-knowledge/import-text-data/readme' + | '/self-host/use-dify/knowledge/create-knowledge/import-text-data/sync-from-notion' + | '/self-host/use-dify/knowledge/create-knowledge/import-text-data/sync-from-website' + | '/self-host/use-dify/knowledge/create-knowledge/introduction' + | '/self-host/use-dify/knowledge/create-knowledge/setting-indexing-methods' + | '/self-host/use-dify/knowledge/external-knowledge-api' + | '/self-host/use-dify/knowledge/integrate-knowledge-within-application' + | '/self-host/use-dify/knowledge/knowledge-pipeline/authorize-data-source' + | '/self-host/use-dify/knowledge/knowledge-pipeline/create-knowledge-pipeline' + | '/self-host/use-dify/knowledge/knowledge-pipeline/knowledge-pipeline-orchestration' + | '/self-host/use-dify/knowledge/knowledge-pipeline/manage-knowledge-base' + | '/self-host/use-dify/knowledge/knowledge-pipeline/publish-knowledge-pipeline' + | '/self-host/use-dify/knowledge/knowledge-pipeline/readme' + | '/self-host/use-dify/knowledge/knowledge-pipeline/upload-files' + | '/self-host/use-dify/knowledge/manage-knowledge/introduction' + | '/self-host/use-dify/knowledge/manage-knowledge/maintain-dataset-via-api' + | '/self-host/use-dify/knowledge/manage-knowledge/maintain-knowledge-documents' + | '/self-host/use-dify/knowledge/metadata' + | '/self-host/use-dify/knowledge/readme' + | '/self-host/use-dify/knowledge/test-retrieval' + | '/self-host/use-dify/monitor/analysis' + | '/self-host/use-dify/monitor/annotation-reply' + | '/self-host/use-dify/monitor/integrations/integrate-aliyun' + | '/self-host/use-dify/monitor/integrations/integrate-arize' + | '/self-host/use-dify/monitor/integrations/integrate-langfuse' + | '/self-host/use-dify/monitor/integrations/integrate-langsmith' + | '/self-host/use-dify/monitor/integrations/integrate-opik' + | '/self-host/use-dify/monitor/integrations/integrate-phoenix' + | '/self-host/use-dify/monitor/integrations/integrate-weave' + | '/self-host/use-dify/monitor/logs' + | '/self-host/use-dify/nodes/agent' + | '/self-host/use-dify/nodes/answer' + | '/self-host/use-dify/nodes/code' + | '/self-host/use-dify/nodes/doc-extractor' + | '/self-host/use-dify/nodes/http-request' + | '/self-host/use-dify/nodes/human-input' + | '/self-host/use-dify/nodes/ifelse' + | '/self-host/use-dify/nodes/iteration' + | '/self-host/use-dify/nodes/knowledge-retrieval' + | '/self-host/use-dify/nodes/list-operator' + | '/self-host/use-dify/nodes/llm' + | '/self-host/use-dify/nodes/loop' + | '/self-host/use-dify/nodes/output' + | '/self-host/use-dify/nodes/parameter-extractor' + | '/self-host/use-dify/nodes/question-classifier' + | '/self-host/use-dify/nodes/template' + | '/self-host/use-dify/nodes/tools' + | '/self-host/use-dify/nodes/trigger/overview' + | '/self-host/use-dify/nodes/trigger/plugin-trigger' + | '/self-host/use-dify/nodes/trigger/schedule-trigger' + | '/self-host/use-dify/nodes/trigger/webhook-trigger' + | '/self-host/use-dify/nodes/user-input' + | '/self-host/use-dify/nodes/variable-aggregator' + | '/self-host/use-dify/nodes/variable-assigner' + | '/self-host/use-dify/publish/README' + | '/self-host/use-dify/publish/developing-with-apis' + | '/self-host/use-dify/publish/publish-mcp' + | '/self-host/use-dify/publish/publish-to-marketplace' + | '/self-host/use-dify/publish/webapp/chatflow-webapp' + | '/self-host/use-dify/publish/webapp/embedding-in-websites' + | '/self-host/use-dify/publish/webapp/web-app-settings' + | '/self-host/use-dify/publish/webapp/workflow-webapp' + | '/self-host/use-dify/workspace/api-extension/api-extension' + | '/self-host/use-dify/workspace/api-extension/cloudflare-worker' + | '/self-host/use-dify/workspace/api-extension/external-data-tool-api-extension' + | '/self-host/use-dify/workspace/api-extension/moderation-api-extension' + | '/self-host/use-dify/workspace/app-management' + | '/self-host/use-dify/workspace/model-providers' + | '/self-host/use-dify/workspace/personal-account-management' + | '/self-host/use-dify/workspace/plugins' + | '/self-host/use-dify/workspace/readme' + | '/self-host/use-dify/workspace/team-members-management' + | '/self-host/use-dify/workspace/tools' + +// Deploy paths +type DeployPath = + | '/deploy/advanced-deployments/local-source-code' + | '/deploy/advanced-deployments/start-the-frontend-docker-container' + | '/deploy/configuration/environments' + | '/deploy/overview' + | '/deploy/platform-guides/bt-panel' + | '/deploy/platform-guides/dify-premium' + | '/deploy/quick-start/docker-compose' + | '/deploy/quick-start/faqs' + | '/deploy/troubleshooting/common-issues' + | '/deploy/troubleshooting/docker-issues' + | '/deploy/troubleshooting/integrations' + | '/deploy/troubleshooting/storage-and-migration' + | '/deploy/troubleshooting/weaviate-v4-migration' + // API Reference paths (English, use apiReferencePathTranslations for other languages) type ApiReferencePath = | '/api-reference/annotations/configure-annotation-reply' @@ -189,6 +413,12 @@ type ApiReferencePath = | '/api-reference/applications/get-app-meta' | '/api-reference/applications/get-app-parameters' | '/api-reference/applications/get-app-webapp-settings' + | '/api-reference/chatflows/get-next-suggested-questions' + | '/api-reference/chatflows/get-workflow-run-detail' + | '/api-reference/chatflows/list-workflow-logs' + | '/api-reference/chatflows/send-chat-message' + | '/api-reference/chatflows/stop-chat-message-generation' + | '/api-reference/chatflows/stream-workflow-events' | '/api-reference/chats/get-next-suggested-questions' | '/api-reference/chats/send-chat-message' | '/api-reference/chats/stop-chat-message-generation' @@ -225,6 +455,8 @@ type ApiReferencePath = | '/api-reference/feedback/submit-message-feedback' | '/api-reference/files/download-file' | '/api-reference/files/upload-file' + | '/api-reference/human-input/get-human-input-form' + | '/api-reference/human-input/submit-human-input-form' | '/api-reference/knowledge-bases/create-an-empty-knowledge-base' | '/api-reference/knowledge-bases/delete-knowledge-base' | '/api-reference/knowledge-bases/get-knowledge-base' @@ -252,19 +484,24 @@ type ApiReferencePath = | '/api-reference/tags/update-knowledge-tag' | '/api-reference/tts/convert-audio-to-text' | '/api-reference/tts/convert-text-to-audio' - | '/api-reference/workflow-runs/get-workflow-run-detail' - | '/api-reference/workflow-runs/list-workflow-logs' | '/api-reference/workflows/get-workflow-run-detail' | '/api-reference/workflows/list-workflow-logs' | '/api-reference/workflows/run-workflow' | '/api-reference/workflows/run-workflow-by-id' | '/api-reference/workflows/stop-workflow-task' + | '/api-reference/workflows/stream-workflow-events' // Base path without language prefix type DocPathWithoutLangBase = + | CloudPath | UseDifyPath - | SelfHostPath + | HomePath + | LearnPath + | QuickStartPath + | CliPath | DevelopPluginPath + | SelfHostPath + | DeployPath | ApiReferencePath // Combined path without language prefix (supports optional #anchor) @@ -272,6 +509,116 @@ export type DocPathWithoutLang = | DocPathWithoutLangBase | `${DocPathWithoutLangBase}#${string}` +// Product availability for productless docs paths +export const docPathProductAvailability: Record = { + '/deploy/advanced-deployments/local-source-code': ['self-host'], + '/deploy/advanced-deployments/start-the-frontend-docker-container': ['self-host'], + '/deploy/configuration/environments': ['self-host'], + '/deploy/overview': ['self-host'], + '/deploy/platform-guides/bt-panel': ['self-host'], + '/deploy/platform-guides/dify-premium': ['self-host'], + '/deploy/quick-start/docker-compose': ['self-host'], + '/deploy/quick-start/faqs': ['self-host'], + '/deploy/troubleshooting/common-issues': ['self-host'], + '/deploy/troubleshooting/docker-issues': ['self-host'], + '/deploy/troubleshooting/integrations': ['self-host'], + '/deploy/troubleshooting/storage-and-migration': ['self-host'], + '/deploy/troubleshooting/weaviate-v4-migration': ['self-host'], + '/use-dify/build/additional-features': ['cloud', 'self-host'], + '/use-dify/build/agent': ['cloud', 'self-host'], + '/use-dify/build/chatbot': ['cloud', 'self-host'], + '/use-dify/build/mcp': ['cloud', 'self-host'], + '/use-dify/build/orchestrate-node': ['cloud', 'self-host'], + '/use-dify/build/predefined-error-handling-logic': ['cloud', 'self-host'], + '/use-dify/build/shortcut-key': ['cloud', 'self-host'], + '/use-dify/build/text-generator': ['cloud', 'self-host'], + '/use-dify/build/version-control': ['cloud', 'self-host'], + '/use-dify/build/workflow-chatflow': ['cloud', 'self-host'], + '/use-dify/build/workflow-collaboration': ['cloud', 'self-host'], + '/use-dify/debug/error-type': ['cloud', 'self-host'], + '/use-dify/debug/history-and-logs': ['cloud', 'self-host'], + '/use-dify/debug/step-run': ['cloud', 'self-host'], + '/use-dify/debug/variable-inspect': ['cloud', 'self-host'], + '/use-dify/getting-started/introduction': ['cloud', 'self-host'], + '/use-dify/knowledge/connect-external-knowledge-base': ['cloud', 'self-host'], + '/use-dify/knowledge/create-knowledge/chunking-and-cleaning-text': ['cloud', 'self-host'], + '/use-dify/knowledge/create-knowledge/import-text-data/readme': ['cloud', 'self-host'], + '/use-dify/knowledge/create-knowledge/import-text-data/sync-from-notion': ['cloud', 'self-host'], + '/use-dify/knowledge/create-knowledge/import-text-data/sync-from-website': ['cloud', 'self-host'], + '/use-dify/knowledge/create-knowledge/introduction': ['cloud', 'self-host'], + '/use-dify/knowledge/create-knowledge/setting-indexing-methods': ['cloud', 'self-host'], + '/use-dify/knowledge/external-knowledge-api': ['cloud', 'self-host'], + '/use-dify/knowledge/integrate-knowledge-within-application': ['cloud', 'self-host'], + '/use-dify/knowledge/knowledge-pipeline/authorize-data-source': ['cloud', 'self-host'], + '/use-dify/knowledge/knowledge-pipeline/create-knowledge-pipeline': ['cloud', 'self-host'], + '/use-dify/knowledge/knowledge-pipeline/knowledge-pipeline-orchestration': ['cloud', 'self-host'], + '/use-dify/knowledge/knowledge-pipeline/manage-knowledge-base': ['cloud', 'self-host'], + '/use-dify/knowledge/knowledge-pipeline/publish-knowledge-pipeline': ['cloud', 'self-host'], + '/use-dify/knowledge/knowledge-pipeline/readme': ['cloud', 'self-host'], + '/use-dify/knowledge/knowledge-pipeline/upload-files': ['cloud', 'self-host'], + '/use-dify/knowledge/knowledge-request-rate-limit': ['cloud'], + '/use-dify/knowledge/manage-knowledge/introduction': ['cloud', 'self-host'], + '/use-dify/knowledge/manage-knowledge/maintain-dataset-via-api': ['cloud', 'self-host'], + '/use-dify/knowledge/manage-knowledge/maintain-knowledge-documents': ['cloud', 'self-host'], + '/use-dify/knowledge/metadata': ['cloud', 'self-host'], + '/use-dify/knowledge/readme': ['cloud', 'self-host'], + '/use-dify/knowledge/test-retrieval': ['cloud', 'self-host'], + '/use-dify/monitor/analysis': ['cloud', 'self-host'], + '/use-dify/monitor/annotation-reply': ['cloud', 'self-host'], + '/use-dify/monitor/integrations/integrate-aliyun': ['cloud', 'self-host'], + '/use-dify/monitor/integrations/integrate-arize': ['cloud', 'self-host'], + '/use-dify/monitor/integrations/integrate-langfuse': ['cloud', 'self-host'], + '/use-dify/monitor/integrations/integrate-langsmith': ['cloud', 'self-host'], + '/use-dify/monitor/integrations/integrate-opik': ['cloud', 'self-host'], + '/use-dify/monitor/integrations/integrate-phoenix': ['cloud', 'self-host'], + '/use-dify/monitor/integrations/integrate-weave': ['cloud', 'self-host'], + '/use-dify/monitor/logs': ['cloud', 'self-host'], + '/use-dify/nodes/agent': ['cloud', 'self-host'], + '/use-dify/nodes/answer': ['cloud', 'self-host'], + '/use-dify/nodes/code': ['cloud', 'self-host'], + '/use-dify/nodes/doc-extractor': ['cloud', 'self-host'], + '/use-dify/nodes/http-request': ['cloud', 'self-host'], + '/use-dify/nodes/human-input': ['cloud', 'self-host'], + '/use-dify/nodes/ifelse': ['cloud', 'self-host'], + '/use-dify/nodes/iteration': ['cloud', 'self-host'], + '/use-dify/nodes/knowledge-retrieval': ['cloud', 'self-host'], + '/use-dify/nodes/list-operator': ['cloud', 'self-host'], + '/use-dify/nodes/llm': ['cloud', 'self-host'], + '/use-dify/nodes/loop': ['cloud', 'self-host'], + '/use-dify/nodes/output': ['cloud', 'self-host'], + '/use-dify/nodes/parameter-extractor': ['cloud', 'self-host'], + '/use-dify/nodes/question-classifier': ['cloud', 'self-host'], + '/use-dify/nodes/template': ['cloud', 'self-host'], + '/use-dify/nodes/tools': ['cloud', 'self-host'], + '/use-dify/nodes/trigger/overview': ['cloud', 'self-host'], + '/use-dify/nodes/trigger/plugin-trigger': ['cloud', 'self-host'], + '/use-dify/nodes/trigger/schedule-trigger': ['cloud', 'self-host'], + '/use-dify/nodes/trigger/webhook-trigger': ['cloud', 'self-host'], + '/use-dify/nodes/user-input': ['cloud', 'self-host'], + '/use-dify/nodes/variable-aggregator': ['cloud', 'self-host'], + '/use-dify/nodes/variable-assigner': ['cloud', 'self-host'], + '/use-dify/publish/README': ['cloud', 'self-host'], + '/use-dify/publish/developing-with-apis': ['cloud', 'self-host'], + '/use-dify/publish/publish-mcp': ['cloud', 'self-host'], + '/use-dify/publish/publish-to-marketplace': ['cloud', 'self-host'], + '/use-dify/publish/webapp/chatflow-webapp': ['cloud', 'self-host'], + '/use-dify/publish/webapp/embedding-in-websites': ['cloud', 'self-host'], + '/use-dify/publish/webapp/web-app-settings': ['cloud', 'self-host'], + '/use-dify/publish/webapp/workflow-webapp': ['cloud', 'self-host'], + '/use-dify/workspace/api-extension/api-extension': ['cloud', 'self-host'], + '/use-dify/workspace/api-extension/cloudflare-worker': ['cloud', 'self-host'], + '/use-dify/workspace/api-extension/external-data-tool-api-extension': ['cloud', 'self-host'], + '/use-dify/workspace/api-extension/moderation-api-extension': ['cloud', 'self-host'], + '/use-dify/workspace/app-management': ['cloud', 'self-host'], + '/use-dify/workspace/model-providers': ['cloud', 'self-host'], + '/use-dify/workspace/personal-account-management': ['cloud', 'self-host'], + '/use-dify/workspace/plugins': ['cloud', 'self-host'], + '/use-dify/workspace/readme': ['cloud', 'self-host'], + '/use-dify/workspace/subscription-management': ['cloud'], + '/use-dify/workspace/team-members-management': ['cloud', 'self-host'], + '/use-dify/workspace/tools': ['cloud', 'self-host'], +} + // API Reference path translations (English -> other languages) export const apiReferencePathTranslations: Record = { '/api-reference/annotations/configure-annotation-reply': { zh: '/api-reference/标注管理/配置标注回复', ja: '/api-reference/アノテーション管理/アノテーション返信を設定' }, @@ -284,6 +631,12 @@ export const apiReferencePathTranslations: Record