fix(web): route Agent V2 documentation links correctly (#41353)

This commit is contained in:
yyh
2026-08-27 06:20:03 +00:00
committed by GitHub
parent 8266d741e8
commit 85b4626ac6
11 changed files with 113 additions and 17 deletions
@@ -98,6 +98,9 @@ describe('useAvailableNodesMetaData', () => {
expect(nodeTypes).not.toContain(BlockEnum.Agent)
expect(result.current.nodesMap?.[BlockEnum.AgentV2]).toBeDefined()
expect(result.current.nodesMap?.[BlockEnum.Agent]).toBeUndefined()
expect(result.current.nodesMap?.[BlockEnum.AgentV2]?.metaData.helpLinkUri).toBe(
'/docs/use-dify/nodes/agent#choose-an-agent',
)
})
it('should expose legacy Agent instead of Agent v2 when Agent v2 is disabled', () => {
@@ -111,5 +114,8 @@ describe('useAvailableNodesMetaData', () => {
expect(nodeTypes).not.toContain(BlockEnum.AgentV2)
expect(result.current.nodesMap?.[BlockEnum.Agent]).toBeDefined()
expect(result.current.nodesMap?.[BlockEnum.AgentV2]).toBeUndefined()
expect(result.current.nodesMap?.[BlockEnum.Agent]?.metaData.helpLinkUri).toBe(
'/docs/use-dify/nodes/agent',
)
})
})
@@ -14,16 +14,14 @@ 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 { isProductlessDocPathWithAnchor } 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
return isProductlessDocPathWithAnchor(helpLinkPath) ? helpLinkPath : undefined
}
export const useAvailableNodesMetaData = () => {
@@ -112,8 +112,8 @@ describe('agent/default', () => {
})
})
it('reuses the legacy agent node help document', () => {
expect(nodeDefault.metaData.helpLinkUri).toBe('agent')
it('links directly to the New Agent section of the shared agent node document', () => {
expect(nodeDefault.metaData.helpLinkUri).toBe('agent#choose-an-agent')
})
it('identifies version 2 agent data as Agent v2', () => {
@@ -8,7 +8,7 @@ import { hasValidAgentBinding } from './types'
const metaData = genNodeMetaData({
sort: 3,
type: BlockEnum.AgentV2,
helpLinkUri: 'agent',
helpLinkUri: 'agent#choose-an-agent',
})
const nodeDefault: NodeDefault<AgentV2NodeType> = {
@@ -2,13 +2,15 @@ import type { BlockEnum } from '@/app/components/workflow/types'
import type { UseDifyNodesPath } from '@/types/doc-paths'
import { BlockClassification } from '@/app/components/workflow/block-selector/types'
type NodeHelpLinkUri = UseDifyNodesPath | `${UseDifyNodesPath}#${string}`
type GenNodeMetaDataParams = {
classification?: BlockClassification
sort: number
type: BlockEnum
title?: string
author?: string
helpLinkUri?: UseDifyNodesPath
helpLinkUri?: NodeHelpLinkUri
isRequired?: boolean
isUndeletable?: boolean
isStart?: boolean
+19
View File
@@ -148,6 +148,25 @@ describe('useDocLink', () => {
})
describe('Product prefix handling', () => {
it.each([
['CLOUD', 'cloud'],
['COMMUNITY', 'self-host'],
] as const)(
'should route New Agent docs in %s edition to %s product docs',
(deploymentEdition, docsProduct) => {
mockDeploymentEdition.value = deploymentEdition
const { result } = renderHook(() => useDocLink())
expect(result.current('/use-dify/build/new-agent/overview')).toBe(
`${defaultDocBaseUrl}/en/${docsProduct}/use-dify/build/new-agent/overview`,
)
expect(result.current('/use-dify/build/new-agent/build#prompt')).toBe(
`${defaultDocBaseUrl}/en/${docsProduct}/use-dify/build/new-agent/build#prompt`,
)
},
)
it('should add cloud product prefix for product docs available in both editions', () => {
mockDeploymentEdition.value = 'CLOUD'
+4 -3
View File
@@ -6,7 +6,7 @@ import { useCallback } from 'react'
import { useTranslation } from '#i18n'
import { deploymentEditionAtom } from '@/features/system-features/state'
import { getDocLanguage, getLanguage, getPricingPageLanguage } from '@/i18n-config/language'
import { docPathProductAvailability } from '@/types/doc-paths'
import { docPathProductAvailability, isProductlessDocPath } from '@/types/doc-paths'
export const useLocale = () => {
const { i18n } = useTranslation()
@@ -52,8 +52,9 @@ const splitPathHash = (path: string) => {
const getProductAwarePath = (path: string, deploymentEdition: DeploymentEdition): string => {
const { pathname, hash } = splitPathHash(path)
const availableProducts = docPathProductAvailability[pathname]
if (!availableProducts?.length) return path
if (!isProductlessDocPath(pathname)) return path
const availableProducts: readonly DocsProduct[] = docPathProductAvailability[pathname]
const currentProduct = getCurrentDocsProduct(deploymentEdition)
const targetProduct = availableProducts.includes(currentProduct)
@@ -0,0 +1,23 @@
import type { ReactElement } from 'react'
import { render, screen } from '@testing-library/react'
import { describe, expect, it, vi } from 'vite-plus/test'
import { AgentConfigureTipContent } from '../tip-content'
vi.mock('react-i18next', () => ({
Trans: ({ components }: { components: { docLink: ReactElement } }) => components.docLink,
}))
vi.mock('@/context/i18n', () => ({
useDocLink: () => (path: string) => `https://docs.example.test/en/cloud${path}`,
}))
describe('AgentConfigureTipContent', () => {
it('links environment variables to the New Agent build documentation', () => {
render(<AgentConfigureTipContent type="env" />)
expect(screen.getByRole('link')).toHaveAttribute(
'href',
'https://docs.example.test/en/cloud/use-dify/build/new-agent/build#environment-variables',
)
})
})
@@ -30,7 +30,9 @@ export function AgentConfigureTipContent({ type }: AgentConfigureTipContentProps
i18nKey={($) => $['agentDetail.configure.advancedSettings.envEditor.richTip']}
ns="agentV2"
components={{
docLink: <DocsLink href={docLink('/use-dify/build/agent')} />,
docLink: (
<DocsLink href={docLink('/use-dify/build/new-agent/build#environment-variables')} />
),
}}
/>
</span>
+17 -1
View File
@@ -367,13 +367,29 @@ function generateTypeDefinitions(
// Generate product availability map for productless runtime links.
lines.push('// Product availability for productless docs paths')
lines.push('export const docPathProductAvailability: Record<string, readonly DocsProduct[]> = {')
lines.push('export const docPathProductAvailability = {')
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('} as const satisfies Record<string, readonly DocsProduct[]>')
lines.push('')
lines.push('export type ProductlessDocPath = keyof typeof docPathProductAvailability')
lines.push('export type ProductlessDocPathWithAnchor =')
lines.push(' | ProductlessDocPath')
// oxlint-disable-next-line no-template-curly-in-string
lines.push(' | `${ProductlessDocPath}#${string}`')
lines.push('')
lines.push('export const isProductlessDocPath = (path: string): path is ProductlessDocPath =>')
lines.push(' Object.hasOwn(docPathProductAvailability, path)')
lines.push('')
lines.push(
'export const isProductlessDocPathWithAnchor = (path: string): path is ProductlessDocPathWithAnchor => {',
)
lines.push(" const pathname = path.split('#', 1)[0]")
lines.push(' return Boolean(pathname && isProductlessDocPath(pathname))')
lines.push('}')
lines.push('')
+33 -4
View File
@@ -2,7 +2,7 @@
// DON NOT EDIT IT MANUALLY
//
// Generated from: https://raw.githubusercontent.com/langgenius/dify-docs/refs/heads/main/docs.json
// Generated at: 2026-07-09T10:55:53.618Z
// Generated at: 2026-08-27T05:58:11.503Z
// Language prefixes
export type DocLanguage = 'en' | 'zh' | 'ja'
@@ -13,9 +13,13 @@ type CloudPath =
| '/cloud/use-dify/build/additional-features'
| '/cloud/use-dify/build/agent'
| '/cloud/use-dify/build/chatbot'
| '/cloud/use-dify/build/goto-anything'
| '/cloud/use-dify/build/new-agent/build'
| '/cloud/use-dify/build/new-agent/overview'
| '/cloud/use-dify/build/orchestrate-node'
| '/cloud/use-dify/build/predefined-error-handling-logic'
| '/cloud/use-dify/build/shortcut-key'
| '/cloud/use-dify/build/snippet'
| '/cloud/use-dify/build/text-generator'
| '/cloud/use-dify/build/version-control'
| '/cloud/use-dify/build/workflow-chatflow'
@@ -41,6 +45,7 @@ type CloudPath =
| '/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/knowledge-storage-limit'
| '/cloud/use-dify/knowledge/manage-knowledge/introduction'
| '/cloud/use-dify/knowledge/manage-knowledge/maintain-knowledge-documents'
| '/cloud/use-dify/knowledge/metadata'
@@ -106,11 +111,14 @@ type UseDifyPath =
| '/use-dify/build/additional-features'
| '/use-dify/build/agent'
| '/use-dify/build/chatbot'
| '/use-dify/build/goto-anything'
| '/use-dify/build/new-agent/build'
| '/use-dify/build/new-agent/overview'
| '/use-dify/build/orchestrate-node'
| '/use-dify/build/predefined-error-handling-logic'
| '/use-dify/build/shortcut-key'
| '/use-dify/build/skills'
| '/use-dify/build/snippet'
| '/use-dify/build/text-generator'
| '/use-dify/build/version-control'
| '/use-dify/build/workflow-chatflow'
@@ -137,6 +145,7 @@ type UseDifyPath =
| '/use-dify/knowledge/knowledge-pipeline/readme'
| '/use-dify/knowledge/knowledge-pipeline/upload-files'
| '/use-dify/knowledge/knowledge-request-rate-limit'
| '/use-dify/knowledge/knowledge-storage-limit'
| '/use-dify/knowledge/manage-knowledge/introduction'
| '/use-dify/knowledge/manage-knowledge/maintain-knowledge-documents'
| '/use-dify/knowledge/metadata'
@@ -324,11 +333,14 @@ type SelfHostPath =
| '/self-host/use-dify/build/additional-features'
| '/self-host/use-dify/build/agent'
| '/self-host/use-dify/build/chatbot'
| '/self-host/use-dify/build/goto-anything'
| '/self-host/use-dify/build/new-agent/build'
| '/self-host/use-dify/build/new-agent/overview'
| '/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/skills'
| '/self-host/use-dify/build/snippet'
| '/self-host/use-dify/build/text-generator'
| '/self-host/use-dify/build/version-control'
| '/self-host/use-dify/build/workflow-chatflow'
@@ -532,7 +544,7 @@ type DocPathWithoutLangBase =
export type DocPathWithoutLang = DocPathWithoutLangBase | `${DocPathWithoutLangBase}#${string}`
// Product availability for productless docs paths
export const docPathProductAvailability: Record<string, readonly DocsProduct[]> = {
export const docPathProductAvailability = {
'/deploy/advanced-deployments/local-source-code': ['self-host'],
'/deploy/advanced-deployments/start-the-frontend-docker-container': ['self-host'],
'/deploy/configuration/environments': ['self-host'],
@@ -549,11 +561,14 @@ export const docPathProductAvailability: Record<string, readonly DocsProduct[]>
'/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/new-agent/build': ['self-host'],
'/use-dify/build/new-agent/overview': ['self-host'],
'/use-dify/build/goto-anything': ['cloud', 'self-host'],
'/use-dify/build/new-agent/build': ['cloud', 'self-host'],
'/use-dify/build/new-agent/overview': ['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/skills': ['self-host'],
'/use-dify/build/snippet': ['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'],
@@ -580,6 +595,7 @@ export const docPathProductAvailability: Record<string, readonly DocsProduct[]>
'/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/knowledge-storage-limit': ['cloud'],
'/use-dify/knowledge/manage-knowledge/introduction': ['cloud', 'self-host'],
'/use-dify/knowledge/manage-knowledge/maintain-knowledge-documents': ['cloud', 'self-host'],
'/use-dify/knowledge/metadata': ['cloud', 'self-host'],
@@ -639,4 +655,17 @@ export const docPathProductAvailability: Record<string, readonly DocsProduct[]>
'/use-dify/workspace/subscription-management': ['cloud'],
'/use-dify/workspace/team-members-management': ['cloud', 'self-host'],
'/use-dify/workspace/tools': ['cloud', 'self-host'],
} as const satisfies Record<string, readonly DocsProduct[]>
export type ProductlessDocPath = keyof typeof docPathProductAvailability
export type ProductlessDocPathWithAnchor = ProductlessDocPath | `${ProductlessDocPath}#${string}`
export const isProductlessDocPath = (path: string): path is ProductlessDocPath =>
Object.hasOwn(docPathProductAvailability, path)
export const isProductlessDocPathWithAnchor = (
path: string,
): path is ProductlessDocPathWithAnchor => {
const pathname = path.split('#', 1)[0]
return Boolean(pathname && isProductlessDocPath(pathname))
}