mirror of
https://github.com/langgenius/dify.git
synced 2026-08-31 01:36:38 +08:00
fix(web): route Agent V2 documentation links correctly (#41353)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
+23
@@ -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',
|
||||
)
|
||||
})
|
||||
})
|
||||
+3
-1
@@ -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>
|
||||
|
||||
@@ -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
@@ -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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user