mirror of
https://github.com/langgenius/dify.git
synced 2026-05-13 08:57:28 +08:00
Reuse shared base controls in MainNav and Integrations, add active integration icons, and keep compact integration content framing covered by targeted tests.
562 lines
22 KiB
TypeScript
562 lines
22 KiB
TypeScript
'use client'
|
||
|
||
import type { Permissions, ReferenceSetting } from '@/app/components/plugins/types'
|
||
import type { IntegrationSection } from '@/app/components/tools/integration-routes'
|
||
import { Button } from '@langgenius/dify-ui/button'
|
||
import { cn } from '@langgenius/dify-ui/cn'
|
||
import { Popover, PopoverContent, PopoverTrigger } from '@langgenius/dify-ui/popover'
|
||
import { ToggleGroup, ToggleGroupItem } from '@langgenius/dify-ui/toggle-group'
|
||
import { parseAsStringLiteral, useQueryState } from 'nuqs'
|
||
import { useMemo, useState } from 'react'
|
||
import { useTranslation } from 'react-i18next'
|
||
import DatasourceIcon from '@/app/components/base/icons/src/vender/workflow/Datasource'
|
||
import InstallPluginDropdown from '@/app/components/plugins/plugin-page/install-plugin-dropdown'
|
||
import useReferenceSetting from '@/app/components/plugins/plugin-page/use-reference-setting'
|
||
import ReferenceSettingModal from '@/app/components/plugins/reference-setting-modal'
|
||
import { PermissionType } from '@/app/components/plugins/types'
|
||
import {
|
||
buildIntegrationPath,
|
||
INTEGRATION_SECTION_VALUES,
|
||
sectionByToolCategory,
|
||
TOOL_CATEGORY_VALUES,
|
||
toolCategoryBySection,
|
||
} from '@/app/components/tools/integration-routes'
|
||
import Link from '@/next/link'
|
||
import { useRouter } from '@/next/navigation'
|
||
import IntegrationSectionRenderer from './integration-section-renderer'
|
||
|
||
type IconComponent = typeof DatasourceIcon
|
||
|
||
const parseAsIntegrationSection = parseAsStringLiteral(INTEGRATION_SECTION_VALUES)
|
||
const parseAsToolCategory = parseAsStringLiteral(TOOL_CATEGORY_VALUES)
|
||
|
||
type NavItem = {
|
||
activeIcon?: IconComponent | string
|
||
disabled?: boolean
|
||
icon: IconComponent | string
|
||
iconClassName?: string
|
||
label: string
|
||
section?: IntegrationSection
|
||
}
|
||
|
||
type PermissionSettingKey = keyof Permissions
|
||
|
||
const permissionSettingOptions = [
|
||
PermissionType.everyone,
|
||
PermissionType.admin,
|
||
PermissionType.noOne,
|
||
] as const
|
||
|
||
const PermissionQuickPanel = ({
|
||
permission,
|
||
onChange,
|
||
}: {
|
||
permission: Permissions
|
||
onChange: (key: PermissionSettingKey, value: PermissionType) => void
|
||
}) => {
|
||
const { t } = useTranslation()
|
||
const rows: Array<{
|
||
key: PermissionSettingKey
|
||
label: string
|
||
value: PermissionType
|
||
}> = [
|
||
{
|
||
key: 'install_permission',
|
||
label: t('privilege.quickWhoCanInstall', { ns: 'plugin' }),
|
||
value: permission.install_permission || PermissionType.noOne,
|
||
},
|
||
{
|
||
key: 'debug_permission',
|
||
label: t('privilege.quickWhoCanDebug', { ns: 'plugin' }),
|
||
value: permission.debug_permission || PermissionType.noOne,
|
||
},
|
||
]
|
||
|
||
return (
|
||
<div className="w-[249px] overflow-hidden rounded-2xl border-t border-components-panel-border bg-components-panel-bg shadow-xl">
|
||
<div className="border-b-[0.5px] border-black/5 py-2">
|
||
<div className="flex flex-col gap-1 px-1 pt-0.5 pb-1">
|
||
<div className="px-3 pt-1 pb-0.5 system-sm-semibold-uppercase text-text-secondary">
|
||
{t('privilege.permissions', { ns: 'plugin' })}
|
||
</div>
|
||
{rows.map(row => (
|
||
<div key={row.key} className="flex flex-col gap-0.5 px-3 py-1">
|
||
<div className="flex min-h-6 items-center system-sm-semibold whitespace-nowrap text-text-secondary">
|
||
{row.label}
|
||
</div>
|
||
<ToggleGroup<PermissionType>
|
||
value={[row.value]}
|
||
onValueChange={(value) => {
|
||
const nextValue = value[0]
|
||
if (nextValue)
|
||
onChange(row.key, nextValue)
|
||
}}
|
||
aria-label={row.label}
|
||
className="w-fit"
|
||
>
|
||
{permissionSettingOptions.map((option) => {
|
||
const optionLabel = t(`privilege.${option}`, { ns: 'plugin' })
|
||
|
||
return (
|
||
<ToggleGroupItem
|
||
key={option}
|
||
value={option}
|
||
aria-label={`${row.label}: ${optionLabel}`}
|
||
className="shrink-0"
|
||
>
|
||
<span className="px-0.5 py-0.5">{optionLabel}</span>
|
||
</ToggleGroupItem>
|
||
)
|
||
})}
|
||
</ToggleGroup>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
const navItemClassName = 'flex h-8 w-full items-center gap-2 rounded-lg py-1 pr-1 pl-3 text-left system-sm-medium transition-colors'
|
||
const activeNavItemClassName = 'bg-state-base-active system-sm-semibold text-components-menu-item-text-active'
|
||
const inactiveNavItemClassName = 'text-components-menu-item-text hover:bg-state-base-hover hover:text-components-menu-item-text-hover'
|
||
const disabledNavItemClassName = 'cursor-not-allowed text-components-menu-item-text-disabled'
|
||
|
||
const buildSectionHref = (section: IntegrationSection) => {
|
||
return buildIntegrationPath(section)
|
||
}
|
||
|
||
type NavLinkItemProps = {
|
||
collapsed?: boolean
|
||
item: NavItem
|
||
section: IntegrationSection
|
||
}
|
||
|
||
const renderIcon = (icon: IconComponent | string, className = 'size-4') => {
|
||
if (typeof icon === 'string')
|
||
return <span className={cn(className, icon)} />
|
||
|
||
const Icon = icon
|
||
return <Icon className={className} />
|
||
}
|
||
|
||
const NavLinkItem = ({ collapsed, item, section }: NavLinkItemProps) => {
|
||
const isActive = item.section === section
|
||
const icon = isActive && item.activeIcon ? item.activeIcon : item.icon
|
||
|
||
const className = cn(
|
||
navItemClassName,
|
||
collapsed && 'justify-center px-0',
|
||
isActive ? activeNavItemClassName : inactiveNavItemClassName,
|
||
)
|
||
|
||
if (!item.section) {
|
||
return (
|
||
<div
|
||
title={item.label}
|
||
aria-label={item.label}
|
||
className={cn(
|
||
navItemClassName,
|
||
collapsed && 'justify-center px-0',
|
||
disabledNavItemClassName,
|
||
)}
|
||
aria-disabled="true"
|
||
>
|
||
<span aria-hidden className="flex size-5 shrink-0 items-center justify-center">
|
||
{renderIcon(item.icon, item.iconClassName)}
|
||
</span>
|
||
{!collapsed && <span className="min-w-0 truncate">{item.label}</span>}
|
||
</div>
|
||
)
|
||
}
|
||
|
||
return (
|
||
<Link
|
||
href={buildSectionHref(item.section)}
|
||
title={item.label}
|
||
aria-label={item.label}
|
||
className={className}
|
||
>
|
||
<span aria-hidden className="flex size-5 shrink-0 items-center justify-center">
|
||
{renderIcon(icon, item.iconClassName)}
|
||
</span>
|
||
{!collapsed && <span className="min-w-0 truncate">{item.label}</span>}
|
||
</Link>
|
||
)
|
||
}
|
||
|
||
type IntegrationsPageProps = {
|
||
section?: IntegrationSection
|
||
}
|
||
|
||
export default function IntegrationsPage({
|
||
section: routeSection,
|
||
}: IntegrationsPageProps) {
|
||
const { t } = useTranslation()
|
||
const router = useRouter()
|
||
const {
|
||
referenceSetting,
|
||
canSetPermissions,
|
||
setReferenceSettings,
|
||
} = useReferenceSetting()
|
||
const [sectionParam] = useQueryState('section', parseAsIntegrationSection)
|
||
const [categoryParam] = useQueryState('category', parseAsToolCategory)
|
||
const section = routeSection ?? sectionParam ?? (categoryParam ? sectionByToolCategory[categoryParam] : 'provider')
|
||
const [providerSearchText, setProviderSearchText] = useState('')
|
||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false)
|
||
const [showPluginSettingModal, setShowPluginSettingModal] = useState(false)
|
||
const providerItem = useMemo<NavItem>(() => ({
|
||
section: 'provider',
|
||
label: t('settings.provider', { ns: 'common' }),
|
||
icon: 'i-ri-brain-2-line',
|
||
activeIcon: 'i-ri-brain-2-fill',
|
||
}), [t])
|
||
const toolItems = useMemo<NavItem[]>(() => [
|
||
{
|
||
section: 'mcp',
|
||
label: 'MCP',
|
||
icon: 'i-custom-vender-integrations-mcp',
|
||
iconClassName: 'h-[14.5px] w-[13.5px]',
|
||
},
|
||
{
|
||
section: 'custom-tool',
|
||
label: t('settings.swaggerAPIAsTool', { ns: 'common' }),
|
||
icon: 'i-custom-vender-integrations-custom-tool',
|
||
activeIcon: 'i-custom-vender-integrations-custom-tool-active',
|
||
iconClassName: 'h-[14.5px] w-[12.5px]',
|
||
},
|
||
{
|
||
section: 'workflow-tool',
|
||
label: t('common.workflowAsTool', { ns: 'workflow' }),
|
||
icon: 'i-custom-vender-integrations-workflow-as-tool',
|
||
activeIcon: 'i-custom-vender-integrations-workflow-as-tool-active',
|
||
iconClassName: 'h-3 w-[12.5px]',
|
||
},
|
||
{
|
||
section: 'api-based-extension',
|
||
label: t('settings.apiBasedExtension', { ns: 'common' }),
|
||
icon: 'i-custom-vender-integrations-api-extension',
|
||
activeIcon: 'i-custom-vender-integrations-api-extension-active',
|
||
iconClassName: 'h-[13px] w-3.5',
|
||
},
|
||
], [t])
|
||
const secondaryItems = useMemo<NavItem[]>(() => [
|
||
{
|
||
section: 'data-source',
|
||
label: t('settings.dataSource', { ns: 'common' }),
|
||
icon: DatasourceIcon,
|
||
activeIcon: 'i-ri-database-2-fill',
|
||
iconClassName: 'size-4',
|
||
},
|
||
{
|
||
section: 'trigger',
|
||
label: t('settings.trigger', { ns: 'common' }),
|
||
icon: 'i-custom-vender-integrations-trigger',
|
||
activeIcon: 'i-custom-vender-integrations-trigger-active',
|
||
iconClassName: 'h-[13.5px] w-[13.5px]',
|
||
},
|
||
{
|
||
section: 'agent-strategy',
|
||
label: t('settings.agentStrategy', { ns: 'common' }),
|
||
icon: 'i-custom-vender-integrations-agent-strategy',
|
||
activeIcon: 'i-custom-vender-integrations-agent-strategy-active',
|
||
iconClassName: 'h-[14.5px] w-[15.5px]',
|
||
},
|
||
{
|
||
section: 'extension',
|
||
label: t('settings.extension', { ns: 'common' }),
|
||
icon: 'i-custom-vender-integrations-extension',
|
||
activeIcon: 'i-custom-vender-integrations-extension-active',
|
||
iconClassName: 'h-[13.5px] w-3',
|
||
},
|
||
], [t])
|
||
const activeItem = [providerItem, ...toolItems, ...secondaryItems].find(item => item.section === section)
|
||
const isToolSection = Boolean(toolCategoryBySection[section])
|
||
const isPluginCategorySection = section === 'trigger' || section === 'agent-strategy' || section === 'extension'
|
||
const useFillLayout = isToolSection || isPluginCategorySection
|
||
const integrationHeader = useMemo(() => {
|
||
switch (section) {
|
||
case 'builtin':
|
||
return {
|
||
title: t('menus.tools', { ns: 'common' }),
|
||
description: t('toolsPage.description', { ns: 'common' }),
|
||
}
|
||
case 'mcp':
|
||
return {
|
||
title: 'MCP',
|
||
description: t('mcpPage.description', { ns: 'common' }),
|
||
}
|
||
case 'custom-tool':
|
||
return {
|
||
title: t('settings.swaggerAPIAsTool', { ns: 'common' }),
|
||
description: t('swaggerAPIAsToolPage.description', { ns: 'common' }),
|
||
}
|
||
case 'workflow-tool':
|
||
return {
|
||
title: t('common.workflowAsTool', { ns: 'workflow' }),
|
||
description: t('workflowAsToolPage.description', { ns: 'common' }),
|
||
}
|
||
case 'api-based-extension':
|
||
return {
|
||
title: t('settings.apiBasedExtension', { ns: 'common' }),
|
||
description: t('apiBasedExtensionPage.description', { ns: 'common' }),
|
||
}
|
||
case 'data-source':
|
||
return {
|
||
title: t('settings.dataSource', { ns: 'common' }),
|
||
description: t('dataSourcePage.description', { ns: 'common' }),
|
||
}
|
||
case 'trigger':
|
||
return {
|
||
title: t('settings.trigger', { ns: 'common' }),
|
||
description: t('triggerPage.description', { ns: 'common' }),
|
||
}
|
||
case 'extension':
|
||
return {
|
||
title: t('settings.extension', { ns: 'common' }),
|
||
description: t('extensionPage.description', { ns: 'common' }),
|
||
}
|
||
case 'agent-strategy':
|
||
return {
|
||
title: t('settings.agentStrategy', { ns: 'common' }),
|
||
description: t('agentStrategyPage.description', { ns: 'common' }),
|
||
}
|
||
default:
|
||
return null
|
||
}
|
||
}, [section, t])
|
||
const showHeaderPluginSetting = (section === 'extension' || section === 'agent-strategy') && canSetPermissions && !!referenceSetting
|
||
const showPermissionQuickPanel = canSetPermissions && !!referenceSetting
|
||
const handlePermissionChange = (key: PermissionSettingKey, value: PermissionType) => {
|
||
if (!referenceSetting)
|
||
return
|
||
|
||
setReferenceSettings({
|
||
...referenceSetting,
|
||
permission: {
|
||
...referenceSetting.permission,
|
||
[key]: value,
|
||
},
|
||
} satisfies ReferenceSetting)
|
||
}
|
||
|
||
return (
|
||
<div className="flex h-full min-h-0 bg-background-body">
|
||
<aside className={cn(
|
||
'flex shrink-0 flex-col border-r border-divider-burn bg-background-body px-2 py-2 transition-[width]',
|
||
sidebarCollapsed ? 'w-14 items-center' : 'w-[200px] items-end',
|
||
)}
|
||
>
|
||
<div className={cn(
|
||
'flex min-h-0 flex-1 flex-col pb-4',
|
||
sidebarCollapsed ? 'w-10' : 'w-[184px]',
|
||
)}
|
||
>
|
||
<div className={cn(
|
||
'flex h-8 shrink-0 items-center py-1',
|
||
sidebarCollapsed ? 'justify-center' : 'justify-between',
|
||
)}
|
||
>
|
||
{!sidebarCollapsed && (
|
||
<div className="title-3xl-semi-bold whitespace-nowrap text-text-primary">
|
||
{t('settings.integrations', { ns: 'common' })}
|
||
</div>
|
||
)}
|
||
<button
|
||
type="button"
|
||
className="flex size-5 items-center justify-center rounded-md text-text-tertiary hover:bg-state-base-hover hover:text-text-secondary"
|
||
aria-label={t(sidebarCollapsed ? 'settings.expand' : 'settings.collapse', { ns: 'common' })}
|
||
title={t(sidebarCollapsed ? 'settings.expand' : 'settings.collapse', { ns: 'common' })}
|
||
onClick={() => setSidebarCollapsed(collapsed => !collapsed)}
|
||
>
|
||
<span
|
||
aria-hidden
|
||
className={cn(
|
||
'i-custom-vender-integrations-panel-left size-[14.5px]',
|
||
sidebarCollapsed && 'rotate-180',
|
||
)}
|
||
/>
|
||
</button>
|
||
</div>
|
||
{!sidebarCollapsed && (
|
||
<div className="mt-6 flex shrink-0 items-center gap-1">
|
||
<InstallPluginDropdown
|
||
rootClassName="min-w-0 flex-1"
|
||
triggerVariant="primary"
|
||
triggerClassName="h-8 min-w-0 gap-0.5 p-2 system-sm-medium"
|
||
triggerLabel={t('installAction', { ns: 'plugin' })}
|
||
triggerOpenClassName="bg-components-button-primary-bg-hover"
|
||
popupClassName="w-[240px] rounded-2xl py-2 shadow-xl"
|
||
onSwitchToMarketplaceTab={() => router.push('/plugins?tab=discover')}
|
||
/>
|
||
<Popover>
|
||
<PopoverTrigger
|
||
render={(
|
||
<Button
|
||
variant="secondary"
|
||
disabled={!showPermissionQuickPanel}
|
||
className="size-8 shrink-0 p-0"
|
||
aria-label={t('privilege.permissions', { ns: 'plugin' })}
|
||
title={t('privilege.permissions', { ns: 'plugin' })}
|
||
>
|
||
<span aria-hidden className="i-ri-equalizer-2-line size-4" />
|
||
</Button>
|
||
)}
|
||
/>
|
||
{showPermissionQuickPanel && (
|
||
<PopoverContent
|
||
placement="bottom-start"
|
||
sideOffset={4}
|
||
popupClassName="border-0 bg-transparent p-0 shadow-none"
|
||
>
|
||
<PermissionQuickPanel
|
||
permission={referenceSetting.permission}
|
||
onChange={handlePermissionChange}
|
||
/>
|
||
</PopoverContent>
|
||
)}
|
||
</Popover>
|
||
</div>
|
||
)}
|
||
<nav className="mt-6 shrink-0 space-y-0.5">
|
||
<NavLinkItem collapsed={sidebarCollapsed} item={providerItem} section={section} />
|
||
<div>
|
||
<Link
|
||
href={buildSectionHref('builtin')}
|
||
title={t('menus.tools', { ns: 'common' })}
|
||
aria-label={t('menus.tools', { ns: 'common' })}
|
||
className={cn(
|
||
navItemClassName,
|
||
sidebarCollapsed && 'justify-center px-0',
|
||
section === 'builtin' ? activeNavItemClassName : inactiveNavItemClassName,
|
||
)}
|
||
>
|
||
<span aria-hidden className="flex size-5 shrink-0 items-center justify-center">
|
||
<span className={cn(
|
||
'h-3.5 w-[12.5px]',
|
||
section === 'builtin' ? 'i-custom-vender-integrations-tools-active' : 'i-custom-vender-integrations-tools',
|
||
)}
|
||
/>
|
||
</span>
|
||
{!sidebarCollapsed && (
|
||
<>
|
||
<span className="min-w-0 flex-1 truncate">{t('menus.tools', { ns: 'common' })}</span>
|
||
</>
|
||
)}
|
||
</Link>
|
||
<div className={cn('space-y-0.5', !sidebarCollapsed && 'pl-6')}>
|
||
{toolItems.map(item => (
|
||
<NavLinkItem
|
||
collapsed={sidebarCollapsed}
|
||
key={item.label}
|
||
item={item}
|
||
section={section}
|
||
/>
|
||
))}
|
||
</div>
|
||
</div>
|
||
{secondaryItems.map(item => (
|
||
<NavLinkItem
|
||
collapsed={sidebarCollapsed}
|
||
key={item.label}
|
||
item={item}
|
||
section={section}
|
||
/>
|
||
))}
|
||
</nav>
|
||
</div>
|
||
{!sidebarCollapsed && (
|
||
<div className="flex min-h-[123px] w-full shrink-0 flex-col items-start gap-2 rounded-xl bg-background-default-hover p-4">
|
||
<div className="relative isolate h-[34.654px] w-[86.251px] shrink-0">
|
||
<div className="absolute top-0 left-[-1px] z-[3] flex size-[34.139px] items-center justify-center">
|
||
<div className="flex size-8 rotate-[-3.97deg] items-center justify-center rounded-lg border border-background-default-subtle bg-background-default-subtle">
|
||
<div className="flex size-full items-center justify-center rounded-lg border-[0.5px] border-divider-regular bg-components-icon-bg-pink-soft p-1 text-[20px] leading-[1.2]">
|
||
🕹️
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className="absolute top-0 left-[26.14px] z-[2] flex size-[34.654px] items-center justify-center">
|
||
<div className="flex size-8 rotate-[4.97deg] items-center justify-center rounded-lg border border-background-default-subtle bg-background-default-subtle">
|
||
<div className="flex size-full items-center justify-center rounded-lg border-[0.5px] border-divider-regular bg-components-icon-bg-orange-dark-soft p-1 text-[20px] leading-[1.2]">
|
||
📙
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className="absolute top-px left-[53.79px] z-[1] flex size-[33.458px] items-center justify-center">
|
||
<div className="flex size-8 rotate-[-2.67deg] items-center justify-center rounded-lg border border-background-default-subtle bg-background-default-subtle">
|
||
<div className="flex size-full items-center justify-center rounded-lg border-[0.5px] border-divider-regular bg-components-icon-bg-teal-soft p-1 text-[20px] leading-[1.2]">
|
||
🤖
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div className="w-full system-xs-medium text-text-secondary">
|
||
{t('settings.discoverMoreIntegrationsInMarketplace', { ns: 'common' })}
|
||
</div>
|
||
</div>
|
||
)}
|
||
</aside>
|
||
<section className="flex min-w-0 flex-1 flex-col overflow-hidden">
|
||
{integrationHeader && (
|
||
<div className="flex min-h-14 shrink-0 items-start border-b border-divider-subtle px-6 pt-2 pb-2">
|
||
<div className="flex min-w-0 flex-1 items-end justify-between gap-3">
|
||
<div className="flex min-w-0 flex-col gap-0.5">
|
||
<div className="system-xl-semibold text-text-primary">
|
||
{integrationHeader.title}
|
||
</div>
|
||
<div className="system-sm-regular text-text-tertiary">
|
||
{integrationHeader.description}
|
||
</div>
|
||
</div>
|
||
{showHeaderPluginSetting && (
|
||
<Button
|
||
variant="secondary"
|
||
className="h-8 shrink-0 gap-0.5 px-3 system-sm-medium"
|
||
onClick={() => setShowPluginSettingModal(true)}
|
||
>
|
||
<span aria-hidden className="i-ri-flashlight-line size-4" />
|
||
<span className="px-0.5">{t('modelProvider.updateSetting', { ns: 'common' })}</span>
|
||
<span className="flex min-w-4 items-center justify-center rounded-[5px] border border-divider-deep bg-components-badge-bg-dimm px-1 py-0.5 system-2xs-medium-uppercase text-text-tertiary">
|
||
{t('autoUpdate.strategy.fixOnly.name', { ns: 'plugin' })}
|
||
</span>
|
||
<span aria-hidden className="i-ri-arrow-down-s-line size-4" />
|
||
</Button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
{!integrationHeader && !isToolSection && (
|
||
<div className="flex min-h-14 shrink-0 items-center justify-between border-b border-divider-subtle px-6 py-2">
|
||
<div>
|
||
<div className="system-xl-semibold text-text-primary">{activeItem?.label}</div>
|
||
{section === 'provider' && (
|
||
<div className="mt-0.5 system-xs-regular text-text-tertiary">
|
||
{t('modelProvider.pageDesc', { ns: 'common' })}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
)}
|
||
<div className={cn(
|
||
'min-h-0 flex-1',
|
||
useFillLayout ? 'flex flex-col overflow-hidden' : 'overflow-y-auto',
|
||
)}
|
||
>
|
||
<IntegrationSectionRenderer
|
||
section={section}
|
||
providerSearchText={providerSearchText}
|
||
onProviderSearchTextChange={setProviderSearchText}
|
||
/>
|
||
</div>
|
||
</section>
|
||
{showPluginSettingModal && referenceSetting && (
|
||
<ReferenceSettingModal
|
||
payload={referenceSetting}
|
||
onHide={() => setShowPluginSettingModal(false)}
|
||
onSave={setReferenceSettings}
|
||
/>
|
||
)}
|
||
</div>
|
||
)
|
||
}
|