'use client' import type { CSSProperties, ReactNode } from 'react' import type { IntegrationSection } from '@/app/components/integrations/routes' import type { DocPathWithoutLang } from '@/types/doc-paths' import { cn } from '@langgenius/dify-ui/cn' import { Collapsible, CollapsiblePanel, CollapsibleTrigger } from '@langgenius/dify-ui/collapsible' import { ScrollArea } from '@langgenius/dify-ui/scroll-area' import { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' import UpdateSettingDialog from '@/app/components/header/account-setting/update-setting-dialog' import { buildIntegrationPath, buildMarketplaceUrlPathByIntegrationSection, toolCategoryBySection, } from '@/app/components/integrations/routes' import { useDocLink } from '@/context/i18n' import Link from '@/next/link' import { useRouter } from '@/next/navigation' import { getMarketplaceUrl } from '@/utils/var' import { STEP_BY_STEP_TOUR_TARGETS } from '../step-by-step-tour/target-registry' import { getPluginCategoryBySection, useIntegrationNav } from './hooks/use-integration-nav' import { useIntegrationPermissions } from './hooks/use-integration-permissions' import { useIntegrationSection } from './hooks/use-integration-section' import IntegrationSectionRenderer from './section-renderer' import { IntegrationSidebarActions, IntegrationSidebarUtilityActions } from './sidebar-actions' import { IntegrationSidebarNavItem } from './sidebar-nav-item' import { integrationSidebarInactiveNavItemClassName, integrationSidebarNavItemClassName, } from './sidebar-nav-item-styles' type IntegrationsPageProps = { onSectionChange?: (section: IntegrationSection) => void onSwitchToMarketplace?: (path: string) => void section?: IntegrationSection } const headerDescriptionDocPaths = { provider: '/use-dify/workspace/model-providers', 'data-source': '/develop-plugin/dev-guides-and-walkthroughs/datasource-plugin#data-source-plugin-types', builtin: '/use-dify/workspace/tools', 'custom-tool': '/use-dify/workspace/tools#swagger-api', 'workflow-tool': '/use-dify/workspace/tools#workflow', mcp: '/use-dify/workspace/tools#mcp', 'custom-endpoint': '/develop-plugin/dev-guides-and-walkthroughs/endpoint', trigger: '/develop-plugin/dev-guides-and-walkthroughs/trigger-plugin', extension: '/use-dify/workspace/api-extension/api-extension', 'agent-strategy': '/develop-plugin/dev-guides-and-walkthroughs/agent-strategy-plugin', } satisfies Partial> type DescriptionWithLearnMoreProps = { children: ReactNode href: string label: string } const DescriptionWithLearnMore = ({ children, href, label }: DescriptionWithLearnMoreProps) => { const title = typeof children === 'string' ? children : undefined return ( {children} {label} ) } function ToolsDisclosureIcon({ className }: { className?: string }) { return ( ) } export default function IntegrationsPage({ onSectionChange, onSwitchToMarketplace, section: routeSection, }: IntegrationsPageProps) { const { t } = useTranslation() const docLink = useDocLink() const router = useRouter() const section = useIntegrationSection(routeSection) const { canDebugger, canInstallPlugin, canDeletePlugin, canUpdatePlugin, handlePermissionChange, isPluginCategory, isReferenceSettingLoading, permission, showPermissionQuickPanel, showPluginCategorySetting, } = useIntegrationPermissions(section) const [providerSearchText, setProviderSearchText] = useState('') const showInstallAction = canInstallPlugin const reserveInstallActionSlot = showInstallAction || isReferenceSettingLoading const showUtilityActions = canDebugger || showPermissionQuickPanel const { activeItem, customEndpointItem, dataSourceItem, integrationHeader, providerItem, secondaryItems, toolItems, } = useIntegrationNav(section) const isToolSection = Boolean(toolCategoryBySection[section]) const [isToolsExpanded, setIsToolsExpanded] = useState(isToolSection) useEffect(() => { if (!isToolSection) return undefined const animationFrame = window.requestAnimationFrame(() => setIsToolsExpanded(true)) return () => window.cancelAnimationFrame(animationFrame) }, [isToolSection]) const useFillLayout = section === 'provider' || section === 'data-source' || section === 'custom-endpoint' || isToolSection || isPluginCategory const scrollAreaLabel = integrationHeader?.title ?? activeItem?.label const sidebarWidthStyle = { '--integrations-sidebar-width': '200px', '--model-provider-warning-left': 'calc(240px + 200px)', } as CSSProperties & Record<'--integrations-sidebar-width' | '--model-provider-warning-left', string> const pluginSettingCategory = getPluginCategoryBySection(section) const pluginSettingAction = showPluginCategorySetting && pluginSettingCategory ? (
) : undefined const marketplaceUrlPath = buildMarketplaceUrlPathByIntegrationSection(section) const headerDescription = integrationHeader?.description ?? (section === 'provider' ? t(($) => $['modelProvider.pageDesc'], { ns: 'common' }) : undefined) const headerDescriptionDocPath = headerDescriptionDocPaths[section] const headerDescriptionWithLink = headerDescription && headerDescriptionDocPath ? ( $['modelProvider.learnMore'], { ns: 'common' })} > {headerDescription} ) : ( headerDescription ) const handleSwitchToMarketplace = () => { if (onSwitchToMarketplace) { onSwitchToMarketplace(marketplaceUrlPath) return } window.open( getMarketplaceUrl(marketplaceUrlPath, undefined, { source: window.location.origin }), '_blank', 'noopener,noreferrer', ) } const handleSelectSection = (nextSection: IntegrationSection) => { if (onSectionChange) { onSectionChange(nextSection) return } router.push(buildIntegrationPath(nextSection)) } const handleToolsOpenChange = (open: boolean) => { setIsToolsExpanded(open) if (open && section !== 'builtin') handleSelectSection('builtin') } const toolsNavItemClassName = cn( integrationSidebarNavItemClassName, integrationSidebarInactiveNavItemClassName, 'group', ) const toolsNavItemContent = ( <> {t(($) => $['menus.tools'], { ns: 'common' })} ) return (
{useFillLayout ? (
) : ( )}
) }