Feat: add check before install plugin (#20014)
This commit is contained in:
@@ -17,9 +17,9 @@ import Loading from '@/app/components/base/loading'
|
||||
import ProviderCard from '@/app/components/plugins/provider-card'
|
||||
import List from '@/app/components/plugins/marketplace/list'
|
||||
import type { Plugin } from '@/app/components/plugins/types'
|
||||
import { MARKETPLACE_URL_PREFIX } from '@/config'
|
||||
import cn from '@/utils/classnames'
|
||||
import { getLocaleOnClient } from '@/i18n'
|
||||
import { getMarketplaceUrl } from '@/utils/var'
|
||||
|
||||
type InstallFromMarketplaceProps = {
|
||||
providers: ModelProvider[]
|
||||
@@ -55,7 +55,7 @@ const InstallFromMarketplace = ({
|
||||
</div>
|
||||
<div className='mb-2 flex items-center pt-2'>
|
||||
<span className='system-sm-regular pr-1 text-text-tertiary'>{t('common.modelProvider.discoverMore')}</span>
|
||||
<Link target="_blank" href={`${MARKETPLACE_URL_PREFIX}${theme ? `?theme=${theme}` : ''}`} className='system-sm-medium inline-flex items-center text-text-accent'>
|
||||
<Link target="_blank" href={getMarketplaceUrl('', { theme })} className='system-sm-medium inline-flex items-center text-text-accent'>
|
||||
{t('plugin.marketplace.difyMarketplace')}
|
||||
<RiArrowRightUpLine className='h-4 w-4' />
|
||||
</Link>
|
||||
|
@@ -15,6 +15,7 @@ import { renderI18nObject } from '@/i18n'
|
||||
import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks'
|
||||
import Partner from '../base/badges/partner'
|
||||
import Verified from '../base/badges/verified'
|
||||
import { RiAlertFill } from '@remixicon/react'
|
||||
|
||||
export type Props = {
|
||||
className?: string
|
||||
@@ -28,6 +29,7 @@ export type Props = {
|
||||
isLoading?: boolean
|
||||
loadingFileName?: string
|
||||
locale?: string
|
||||
limitedInstall?: boolean
|
||||
}
|
||||
|
||||
const Card = ({
|
||||
@@ -42,6 +44,7 @@ const Card = ({
|
||||
isLoading = false,
|
||||
loadingFileName,
|
||||
locale: localeFromProps,
|
||||
limitedInstall = false,
|
||||
}: Props) => {
|
||||
const defaultLocale = useGetLanguage()
|
||||
const locale = localeFromProps ? getLanguage(localeFromProps) : defaultLocale
|
||||
@@ -54,7 +57,7 @@ const Card = ({
|
||||
obj ? renderI18nObject(obj, locale) : ''
|
||||
const isPartner = badges.includes('partner')
|
||||
|
||||
const wrapClassName = cn('hover-bg-components-panel-on-panel-item-bg relative rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg p-4 pb-3 shadow-xs', className)
|
||||
const wrapClassName = cn('hover-bg-components-panel-on-panel-item-bg relative overflow-hidden rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-on-panel-item-bg shadow-xs', className)
|
||||
if (isLoading) {
|
||||
return (
|
||||
<Placeholder
|
||||
@@ -66,30 +69,39 @@ const Card = ({
|
||||
|
||||
return (
|
||||
<div className={wrapClassName}>
|
||||
{!hideCornerMark && <CornerMark text={cornerMark} />}
|
||||
{/* Header */}
|
||||
<div className="flex">
|
||||
<Icon src={icon} installed={installed} installFailed={installFailed} />
|
||||
<div className="ml-3 w-0 grow">
|
||||
<div className="flex h-5 items-center">
|
||||
<Title title={getLocalizedText(label)} />
|
||||
{isPartner && <Partner className='ml-0.5 h-4 w-4' text={t('plugin.marketplace.partnerTip')} />}
|
||||
{verified && <Verified className='ml-0.5 h-4 w-4' text={t('plugin.marketplace.verifiedTip')} />}
|
||||
{titleLeft} {/* This can be version badge */}
|
||||
<div className={cn('p-4 pb-3', limitedInstall && 'pb-1')}>
|
||||
{!hideCornerMark && <CornerMark text={cornerMark} />}
|
||||
{/* Header */}
|
||||
<div className="flex">
|
||||
<Icon src={icon} installed={installed} installFailed={installFailed} />
|
||||
<div className="ml-3 w-0 grow">
|
||||
<div className="flex h-5 items-center">
|
||||
<Title title={getLocalizedText(label)} />
|
||||
{isPartner && <Partner className='ml-0.5 h-4 w-4' text={t('plugin.marketplace.partnerTip')} />}
|
||||
{verified && <Verified className='ml-0.5 h-4 w-4' text={t('plugin.marketplace.verifiedTip')} />}
|
||||
{titleLeft} {/* This can be version badge */}
|
||||
</div>
|
||||
<OrgInfo
|
||||
className="mt-0.5"
|
||||
orgName={org}
|
||||
packageName={name}
|
||||
/>
|
||||
</div>
|
||||
<OrgInfo
|
||||
className="mt-0.5"
|
||||
orgName={org}
|
||||
packageName={name}
|
||||
/>
|
||||
</div>
|
||||
<Description
|
||||
className="mt-3"
|
||||
text={getLocalizedText(brief)}
|
||||
descriptionLineRows={descriptionLineRows}
|
||||
/>
|
||||
{footer && <div>{footer}</div>}
|
||||
</div>
|
||||
<Description
|
||||
className="mt-3"
|
||||
text={getLocalizedText(brief)}
|
||||
descriptionLineRows={descriptionLineRows}
|
||||
/>
|
||||
{footer && <div>{footer}</div>}
|
||||
{limitedInstall
|
||||
&& <div className='relative flex h-8 items-center gap-x-2 px-3 after:absolute after:bottom-0 after:left-0 after:right-0 after:top-0 after:bg-toast-warning-bg after:opacity-40'>
|
||||
<RiAlertFill className='h-3 w-3 shrink-0 text-text-warning-secondary' />
|
||||
<p className='system-xs-regular z-10 grow text-text-secondary'>
|
||||
{t('plugin.installModal.installWarning')}
|
||||
</p>
|
||||
</div>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@@ -0,0 +1,46 @@
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import type { SystemFeatures } from '@/types/feature'
|
||||
import { InstallationScope } from '@/types/feature'
|
||||
import type { Plugin, PluginManifestInMarket } from '../../types'
|
||||
|
||||
type PluginProps = (Plugin | PluginManifestInMarket) & { from: 'github' | 'marketplace' | 'package' }
|
||||
|
||||
export function pluginInstallLimit(plugin: PluginProps, systemFeatures: SystemFeatures) {
|
||||
if (systemFeatures.plugin_installation_permission.restrict_to_marketplace_only) {
|
||||
if (plugin.from === 'github' || plugin.from === 'package')
|
||||
return { canInstall: false }
|
||||
}
|
||||
|
||||
if (systemFeatures.plugin_installation_permission.plugin_installation_scope === InstallationScope.ALL) {
|
||||
return {
|
||||
canInstall: true,
|
||||
}
|
||||
}
|
||||
if (systemFeatures.plugin_installation_permission.plugin_installation_scope === InstallationScope.NONE) {
|
||||
return {
|
||||
canInstall: false,
|
||||
}
|
||||
}
|
||||
const verification = plugin.verification || {}
|
||||
if (!plugin.verification || !plugin.verification.authorized_category)
|
||||
verification.authorized_category = 'langgenius'
|
||||
|
||||
if (systemFeatures.plugin_installation_permission.plugin_installation_scope === InstallationScope.OFFICIAL_ONLY) {
|
||||
return {
|
||||
canInstall: verification.authorized_category === 'langgenius',
|
||||
}
|
||||
}
|
||||
if (systemFeatures.plugin_installation_permission.plugin_installation_scope === InstallationScope.OFFICIAL_AND_PARTNER) {
|
||||
return {
|
||||
canInstall: verification.authorized_category === 'langgenius' || verification.authorized_category === 'partner',
|
||||
}
|
||||
}
|
||||
return {
|
||||
canInstall: true,
|
||||
}
|
||||
}
|
||||
|
||||
export default function usePluginInstallLimit(plugin: PluginProps) {
|
||||
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
|
||||
return pluginInstallLimit(plugin, systemFeatures)
|
||||
}
|
@@ -39,7 +39,7 @@ const Item: FC<Props> = ({
|
||||
plugin_id: data.unique_identifier,
|
||||
}
|
||||
onFetchedPayload(payload)
|
||||
setPayload(payload)
|
||||
setPayload({ ...payload, from: dependency.type })
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [data])
|
||||
|
@@ -8,6 +8,7 @@ import useGetIcon from '../../base/use-get-icon'
|
||||
import { MARKETPLACE_API_PREFIX } from '@/config'
|
||||
import Version from '../../base/version'
|
||||
import type { VersionProps } from '../../../types'
|
||||
import usePluginInstallLimit from '../../hooks/use-install-plugin-limit'
|
||||
|
||||
type Props = {
|
||||
checked: boolean
|
||||
@@ -29,9 +30,11 @@ const LoadedItem: FC<Props> = ({
|
||||
...particleVersionInfo,
|
||||
toInstallVersion: payload.version,
|
||||
}
|
||||
const { canInstall } = usePluginInstallLimit(payload)
|
||||
return (
|
||||
<div className='flex items-center space-x-2'>
|
||||
<Checkbox
|
||||
disabled={!canInstall}
|
||||
className='shrink-0'
|
||||
checked={checked}
|
||||
onCheck={() => onCheckedChange(payload)}
|
||||
@@ -43,6 +46,7 @@ const LoadedItem: FC<Props> = ({
|
||||
icon: isFromMarketPlace ? `${MARKETPLACE_API_PREFIX}/plugins/${payload.org}/${payload.name}/icon` : getIconUrl(payload.icon),
|
||||
}}
|
||||
titleLeft={payload.version ? <Version {...versionInfo} /> : null}
|
||||
limitedInstall={!canInstall}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
@@ -29,7 +29,7 @@ const PackageItem: FC<Props> = ({
|
||||
const plugin = pluginManifestToCardPluginProps(payload.value.manifest)
|
||||
return (
|
||||
<LoadedItem
|
||||
payload={plugin}
|
||||
payload={{ ...plugin, from: payload.type }}
|
||||
checked={checked}
|
||||
onCheckedChange={onCheckedChange}
|
||||
isFromMarketPlace={isFromMarketPlace}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import type { ForwardRefRenderFunction } from 'react'
|
||||
import { useImperativeHandle } from 'react'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
import type { Dependency, GitHubItemAndMarketPlaceDependency, PackageDependency, Plugin, VersionInfo } from '../../../types'
|
||||
import MarketplaceItem from '../item/marketplace-item'
|
||||
@@ -9,22 +10,34 @@ import useCheckInstalled from '@/app/components/plugins/install-plugin/hooks/use
|
||||
import produce from 'immer'
|
||||
import PackageItem from '../item/package-item'
|
||||
import LoadingError from '../../base/loading-error'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { pluginInstallLimit } from '../../hooks/use-install-plugin-limit'
|
||||
|
||||
type Props = {
|
||||
allPlugins: Dependency[]
|
||||
selectedPlugins: Plugin[]
|
||||
onSelect: (plugin: Plugin, selectedIndex: number) => void
|
||||
onSelect: (plugin: Plugin, selectedIndex: number, allCanInstallPluginsLength: number) => void
|
||||
onSelectAll: (plugins: Plugin[], selectedIndexes: number[]) => void
|
||||
onDeSelectAll: () => void
|
||||
onLoadedAllPlugin: (installedInfo: Record<string, VersionInfo>) => void
|
||||
isFromMarketPlace?: boolean
|
||||
}
|
||||
|
||||
const InstallByDSLList: FC<Props> = ({
|
||||
export type ExposeRefs = {
|
||||
selectAllPlugins: () => void
|
||||
deSelectAllPlugins: () => void
|
||||
}
|
||||
|
||||
const InstallByDSLList: ForwardRefRenderFunction<ExposeRefs, Props> = ({
|
||||
allPlugins,
|
||||
selectedPlugins,
|
||||
onSelect,
|
||||
onSelectAll,
|
||||
onDeSelectAll,
|
||||
onLoadedAllPlugin,
|
||||
isFromMarketPlace,
|
||||
}) => {
|
||||
}, ref) => {
|
||||
const systemFeatures = useGlobalPublicStore(s => s.systemFeatures)
|
||||
// DSL has id, to get plugin info to show more info
|
||||
const { isLoading: isFetchingMarketplaceDataById, data: infoGetById, error: infoByIdError } = useFetchPluginsInMarketPlaceByInfo(allPlugins.filter(d => d.type === 'marketplace').map((d) => {
|
||||
const dependecy = (d as GitHubItemAndMarketPlaceDependency).value
|
||||
@@ -97,7 +110,8 @@ const InstallByDSLList: FC<Props> = ({
|
||||
const sortedList = allPlugins.filter(d => d.type === 'marketplace').map((d) => {
|
||||
const p = d as GitHubItemAndMarketPlaceDependency
|
||||
const id = p.value.marketplace_plugin_unique_identifier?.split(':')[0]
|
||||
return infoGetById.data.list.find(item => item.plugin.plugin_id === id)?.plugin
|
||||
const retPluginInfo = infoGetById.data.list.find(item => item.plugin.plugin_id === id)?.plugin
|
||||
return { ...retPluginInfo, from: d.type } as Plugin
|
||||
})
|
||||
const payloads = sortedList
|
||||
const failedIndex: number[] = []
|
||||
@@ -106,7 +120,7 @@ const InstallByDSLList: FC<Props> = ({
|
||||
if (payloads[i]) {
|
||||
draft[index] = {
|
||||
...payloads[i],
|
||||
version: payloads[i].version || payloads[i].latest_version,
|
||||
version: payloads[i]!.version || payloads[i]!.latest_version,
|
||||
}
|
||||
}
|
||||
else { failedIndex.push(index) }
|
||||
@@ -181,9 +195,35 @@ const InstallByDSLList: FC<Props> = ({
|
||||
|
||||
const handleSelect = useCallback((index: number) => {
|
||||
return () => {
|
||||
onSelect(plugins[index]!, index)
|
||||
const canSelectPlugins = plugins.filter((p) => {
|
||||
const { canInstall } = pluginInstallLimit(p!, systemFeatures)
|
||||
return canInstall
|
||||
})
|
||||
onSelect(plugins[index]!, index, canSelectPlugins.length)
|
||||
}
|
||||
}, [onSelect, plugins])
|
||||
}, [onSelect, plugins, systemFeatures])
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
selectAllPlugins: () => {
|
||||
const selectedIndexes: number[] = []
|
||||
const selectedPlugins: Plugin[] = []
|
||||
allPlugins.forEach((d, index) => {
|
||||
const p = plugins[index]
|
||||
if (!p)
|
||||
return
|
||||
const { canInstall } = pluginInstallLimit(p, systemFeatures)
|
||||
if (canInstall) {
|
||||
selectedIndexes.push(index)
|
||||
selectedPlugins.push(p)
|
||||
}
|
||||
})
|
||||
onSelectAll(selectedPlugins, selectedIndexes)
|
||||
},
|
||||
deSelectAllPlugins: () => {
|
||||
onDeSelectAll()
|
||||
},
|
||||
}))
|
||||
|
||||
return (
|
||||
<>
|
||||
{allPlugins.map((d, index) => {
|
||||
@@ -211,7 +251,7 @@ const InstallByDSLList: FC<Props> = ({
|
||||
key={index}
|
||||
checked={!!selectedPlugins.find(p => p.plugin_id === plugins[index]?.plugin_id)}
|
||||
onCheckedChange={handleSelect(index)}
|
||||
payload={plugin}
|
||||
payload={{ ...plugin, from: d.type } as Plugin}
|
||||
version={(d as GitHubItemAndMarketPlaceDependency).value.version! || plugin?.version || ''}
|
||||
versionInfo={getVersionInfo(`${plugin?.org || plugin?.author}/${plugin?.name}`)}
|
||||
/>
|
||||
@@ -234,4 +274,4 @@ const InstallByDSLList: FC<Props> = ({
|
||||
</>
|
||||
)
|
||||
}
|
||||
export default React.memo(InstallByDSLList)
|
||||
export default React.forwardRef(InstallByDSLList)
|
||||
|
@@ -1,15 +1,18 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import { useRef } from 'react'
|
||||
import React, { useCallback, useState } from 'react'
|
||||
import type { Dependency, InstallStatusResponse, Plugin, VersionInfo } from '../../../types'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { RiLoader2Line } from '@remixicon/react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import type { ExposeRefs } from './install-multi'
|
||||
import InstallMulti from './install-multi'
|
||||
import { useInstallOrUpdate } from '@/service/use-plugins'
|
||||
import useRefreshPluginList from '../../hooks/use-refresh-plugin-list'
|
||||
import { useCanInstallPluginFromMarketplace } from '@/app/components/plugins/plugin-page/use-permission'
|
||||
import { useMittContextSelector } from '@/context/mitt-context'
|
||||
import Checkbox from '@/app/components/base/checkbox'
|
||||
const i18nPrefix = 'plugin.installModal'
|
||||
|
||||
type Props = {
|
||||
@@ -34,18 +37,8 @@ const Install: FC<Props> = ({
|
||||
const [selectedPlugins, setSelectedPlugins] = React.useState<Plugin[]>([])
|
||||
const [selectedIndexes, setSelectedIndexes] = React.useState<number[]>([])
|
||||
const selectedPluginsNum = selectedPlugins.length
|
||||
const installMultiRef = useRef<ExposeRefs>(null)
|
||||
const { refreshPluginList } = useRefreshPluginList()
|
||||
const handleSelect = (plugin: Plugin, selectedIndex: number) => {
|
||||
const isSelected = !!selectedPlugins.find(p => p.plugin_id === plugin.plugin_id)
|
||||
let nextSelectedPlugins
|
||||
if (isSelected)
|
||||
nextSelectedPlugins = selectedPlugins.filter(p => p.plugin_id !== plugin.plugin_id)
|
||||
else
|
||||
nextSelectedPlugins = [...selectedPlugins, plugin]
|
||||
setSelectedPlugins(nextSelectedPlugins)
|
||||
const nextSelectedIndexes = isSelected ? selectedIndexes.filter(i => i !== selectedIndex) : [...selectedIndexes, selectedIndex]
|
||||
setSelectedIndexes(nextSelectedIndexes)
|
||||
}
|
||||
|
||||
const [canInstall, setCanInstall] = React.useState(false)
|
||||
const [installedInfo, setInstalledInfo] = useState<Record<string, VersionInfo> | undefined>(undefined)
|
||||
@@ -81,6 +74,51 @@ const Install: FC<Props> = ({
|
||||
installedInfo: installedInfo!,
|
||||
})
|
||||
}
|
||||
const [isSelectAll, setIsSelectAll] = useState(false)
|
||||
const [isIndeterminate, setIsIndeterminate] = useState(false)
|
||||
const handleClickSelectAll = useCallback(() => {
|
||||
if (isSelectAll)
|
||||
installMultiRef.current?.deSelectAllPlugins()
|
||||
else
|
||||
installMultiRef.current?.selectAllPlugins()
|
||||
}, [isSelectAll])
|
||||
const handleSelectAll = useCallback((plugins: Plugin[], selectedIndexes: number[]) => {
|
||||
setSelectedPlugins(plugins)
|
||||
setSelectedIndexes(selectedIndexes)
|
||||
setIsSelectAll(true)
|
||||
setIsIndeterminate(false)
|
||||
}, [])
|
||||
const handleDeSelectAll = useCallback(() => {
|
||||
setSelectedPlugins([])
|
||||
setSelectedIndexes([])
|
||||
setIsSelectAll(false)
|
||||
setIsIndeterminate(false)
|
||||
}, [])
|
||||
|
||||
const handleSelect = useCallback((plugin: Plugin, selectedIndex: number, allPluginsLength: number) => {
|
||||
const isSelected = !!selectedPlugins.find(p => p.plugin_id === plugin.plugin_id)
|
||||
let nextSelectedPlugins
|
||||
if (isSelected)
|
||||
nextSelectedPlugins = selectedPlugins.filter(p => p.plugin_id !== plugin.plugin_id)
|
||||
else
|
||||
nextSelectedPlugins = [...selectedPlugins, plugin]
|
||||
setSelectedPlugins(nextSelectedPlugins)
|
||||
const nextSelectedIndexes = isSelected ? selectedIndexes.filter(i => i !== selectedIndex) : [...selectedIndexes, selectedIndex]
|
||||
setSelectedIndexes(nextSelectedIndexes)
|
||||
if (nextSelectedPlugins.length === 0) {
|
||||
setIsSelectAll(false)
|
||||
setIsIndeterminate(false)
|
||||
}
|
||||
else if (nextSelectedPlugins.length === allPluginsLength) {
|
||||
setIsSelectAll(true)
|
||||
setIsIndeterminate(false)
|
||||
}
|
||||
else {
|
||||
setIsIndeterminate(true)
|
||||
setIsSelectAll(false)
|
||||
}
|
||||
}, [selectedPlugins, selectedIndexes])
|
||||
|
||||
const { canInstallPluginFromMarketplace } = useCanInstallPluginFromMarketplace()
|
||||
return (
|
||||
<>
|
||||
@@ -90,9 +128,12 @@ const Install: FC<Props> = ({
|
||||
</div>
|
||||
<div className='w-full space-y-1 rounded-2xl bg-background-section-burn p-2'>
|
||||
<InstallMulti
|
||||
ref={installMultiRef}
|
||||
allPlugins={allPlugins}
|
||||
selectedPlugins={selectedPlugins}
|
||||
onSelect={handleSelect}
|
||||
onSelectAll={handleSelectAll}
|
||||
onDeSelectAll={handleDeSelectAll}
|
||||
onLoadedAllPlugin={handleLoadedAllPlugin}
|
||||
isFromMarketPlace={isFromMarketPlace}
|
||||
/>
|
||||
@@ -100,21 +141,29 @@ const Install: FC<Props> = ({
|
||||
</div>
|
||||
{/* Action Buttons */}
|
||||
{!isHideButton && (
|
||||
<div className='flex items-center justify-end gap-2 self-stretch p-6 pt-5'>
|
||||
{!canInstall && (
|
||||
<Button variant='secondary' className='min-w-[72px]' onClick={onCancel}>
|
||||
{t('common.operation.cancel')}
|
||||
<div className='flex items-center justify-between gap-2 self-stretch p-6 pt-5'>
|
||||
<div className='px-2'>
|
||||
{canInstall && <div className='flex items-center gap-x-2' onClick={handleClickSelectAll}>
|
||||
<Checkbox checked={isSelectAll} indeterminate={isIndeterminate} />
|
||||
<p className='system-sm-medium cursor-pointer text-text-secondary'>{isSelectAll ? t('common.operation.deSelectAll') : t('common.operation.selectAll')}</p>
|
||||
</div>}
|
||||
</div>
|
||||
<div className='flex items-center justify-end gap-2 self-stretch'>
|
||||
{!canInstall && (
|
||||
<Button variant='secondary' className='min-w-[72px]' onClick={onCancel}>
|
||||
{t('common.operation.cancel')}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant='primary'
|
||||
className='flex min-w-[72px] space-x-0.5'
|
||||
disabled={!canInstall || isInstalling || selectedPlugins.length === 0 || !canInstallPluginFromMarketplace}
|
||||
onClick={handleInstall}
|
||||
>
|
||||
{isInstalling && <RiLoader2Line className='h-4 w-4 animate-spin-slow' />}
|
||||
<span>{t(`${i18nPrefix}.${isInstalling ? 'installing' : 'install'}`)}</span>
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
variant='primary'
|
||||
className='flex min-w-[72px] space-x-0.5'
|
||||
disabled={!canInstall || isInstalling || selectedPlugins.length === 0 || !canInstallPluginFromMarketplace}
|
||||
onClick={handleInstall}
|
||||
>
|
||||
{isInstalling && <RiLoader2Line className='h-4 w-4 animate-spin-slow' />}
|
||||
<span>{t(`${i18nPrefix}.${isInstalling ? 'installing' : 'install'}`)}</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
@@ -124,7 +124,7 @@ const Installed: FC<Props> = ({
|
||||
/>
|
||||
</p>
|
||||
{!isDifyVersionCompatible && (
|
||||
<p className='system-md-regular flex items-center gap-1 text-text-secondary text-text-warning'>
|
||||
<p className='system-md-regular flex items-center gap-1 text-text-warning'>
|
||||
{t('plugin.difyVersionNotCompatible', { minimalDifyVersion: payload.meta.minimum_dify_version })}
|
||||
</p>
|
||||
)}
|
||||
|
@@ -15,6 +15,7 @@ import Version from '../../base/version'
|
||||
import { usePluginTaskList } from '@/service/use-plugins'
|
||||
import { gte } from 'semver'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import useInstallPluginLimit from '../../hooks/use-install-plugin-limit'
|
||||
|
||||
const i18nPrefix = 'plugin.installModal'
|
||||
|
||||
@@ -124,15 +125,16 @@ const Installed: FC<Props> = ({
|
||||
const isDifyVersionCompatible = useMemo(() => {
|
||||
if (!pluginDeclaration || !langeniusVersionInfo.current_version) return true
|
||||
return gte(langeniusVersionInfo.current_version, pluginDeclaration?.manifest.meta.minimum_dify_version ?? '0.0.0')
|
||||
}, [langeniusVersionInfo.current_version, pluginDeclaration?.manifest.meta.minimum_dify_version])
|
||||
}, [langeniusVersionInfo.current_version, pluginDeclaration])
|
||||
|
||||
const { canInstall } = useInstallPluginLimit({ ...payload, from: 'marketplace' })
|
||||
return (
|
||||
<>
|
||||
<div className='flex flex-col items-start justify-center gap-4 self-stretch px-6 py-3'>
|
||||
<div className='system-md-regular text-text-secondary'>
|
||||
<p>{t(`${i18nPrefix}.readyToInstall`)}</p>
|
||||
{!isDifyVersionCompatible && (
|
||||
<p className='system-md-regular text-text-secondary text-text-warning'>
|
||||
<p className='system-md-regular text-text-warning'>
|
||||
{t('plugin.difyVersionNotCompatible', { minimalDifyVersion: pluginDeclaration?.manifest.meta.minimum_dify_version })}
|
||||
</p>
|
||||
)}
|
||||
@@ -146,6 +148,7 @@ const Installed: FC<Props> = ({
|
||||
installedVersion={installedVersion}
|
||||
toInstallVersion={toInstallVersion}
|
||||
/>}
|
||||
limitedInstall={!canInstall}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
@@ -159,7 +162,7 @@ const Installed: FC<Props> = ({
|
||||
<Button
|
||||
variant='primary'
|
||||
className='flex min-w-[72px] space-x-0.5'
|
||||
disabled={isInstalling || isLoading}
|
||||
disabled={isInstalling || isLoading || !canInstall}
|
||||
onClick={handleInstall}
|
||||
>
|
||||
{isInstalling && <RiLoader2Line className='h-4 w-4 animate-spin-slow' />}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
import type { Plugin, PluginDeclaration, PluginManifestInMarket } from '../types'
|
||||
import type { GitHubUrlInfo } from '@/app/components/plugins/types'
|
||||
import { isEmpty } from 'lodash-es'
|
||||
|
||||
export const pluginManifestToCardPluginProps = (pluginManifest: PluginDeclaration): Plugin => {
|
||||
return {
|
||||
@@ -47,6 +48,7 @@ export const pluginManifestInMarketToPluginProps = (pluginManifest: PluginManife
|
||||
},
|
||||
tags: [],
|
||||
badges: pluginManifest.badges,
|
||||
verification: isEmpty(pluginManifest.verification) ? { authorized_category: 'langgenius' } : pluginManifest.verification,
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -56,7 +56,7 @@ const CardWrapper = ({
|
||||
>
|
||||
{t('plugin.detailPanel.operation.install')}
|
||||
</Button>
|
||||
<a href={`${getPluginLinkInMarketplace(plugin)}?language=${localeFromLocale}${theme ? `&theme=${theme}` : ''}`} target='_blank' className='block w-[calc(50%-4px)] flex-1 shrink-0'>
|
||||
<a href={getPluginLinkInMarketplace(plugin, { language: localeFromLocale, theme })} target='_blank' className='block w-[calc(50%-4px)] flex-1 shrink-0'>
|
||||
<Button
|
||||
className='w-full gap-0.5'
|
||||
>
|
||||
|
@@ -8,8 +8,8 @@ import type {
|
||||
} from '@/app/components/plugins/marketplace/types'
|
||||
import {
|
||||
MARKETPLACE_API_PREFIX,
|
||||
MARKETPLACE_URL_PREFIX,
|
||||
} from '@/config'
|
||||
import { getMarketplaceUrl } from '@/utils/var'
|
||||
|
||||
export const getPluginIconInMarketplace = (plugin: Plugin) => {
|
||||
if (plugin.type === 'bundle')
|
||||
@@ -32,10 +32,10 @@ export const getFormattedPlugin = (bundle: any) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const getPluginLinkInMarketplace = (plugin: Plugin) => {
|
||||
export const getPluginLinkInMarketplace = (plugin: Plugin, params?: Record<string, string | undefined>) => {
|
||||
if (plugin.type === 'bundle')
|
||||
return `${MARKETPLACE_URL_PREFIX}/bundles/${plugin.org}/${plugin.name}`
|
||||
return `${MARKETPLACE_URL_PREFIX}/plugins/${plugin.org}/${plugin.name}`
|
||||
return getMarketplaceUrl(`/bundles/${plugin.org}/${plugin.name}`, params)
|
||||
return getMarketplaceUrl(`/plugins/${plugin.org}/${plugin.name}`, params)
|
||||
}
|
||||
|
||||
export const getMarketplacePluginsByCollectionId = async (collectionId: string, query?: CollectionsAndPluginsSearchParams) => {
|
||||
|
@@ -33,8 +33,9 @@ import { useGetLanguage } from '@/context/i18n'
|
||||
import { useModalContext } from '@/context/modal-context'
|
||||
import { useProviderContext } from '@/context/provider-context'
|
||||
import { useInvalidateAllToolProviders } from '@/service/use-tools'
|
||||
import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config'
|
||||
import { API_PREFIX } from '@/config'
|
||||
import cn from '@/utils/classnames'
|
||||
import { getMarketplaceUrl } from '@/utils/var'
|
||||
|
||||
const i18nPrefix = 'plugin.action'
|
||||
|
||||
@@ -87,7 +88,7 @@ const DetailHeader = ({
|
||||
if (isFromGitHub)
|
||||
return `https://github.com/${meta!.repo}`
|
||||
if (isFromMarketplace)
|
||||
return `${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}${theme ? `?theme=${theme}` : ''}`
|
||||
return getMarketplaceUrl(`/plugins/${author}/${name}`, { theme })
|
||||
return ''
|
||||
}, [author, isFromGitHub, isFromMarketplace, meta, name, theme])
|
||||
|
||||
|
@@ -21,13 +21,14 @@ import OrgInfo from '../card/base/org-info'
|
||||
import Title from '../card/base/title'
|
||||
import Action from './action'
|
||||
import cn from '@/utils/classnames'
|
||||
import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config'
|
||||
import { API_PREFIX } from '@/config'
|
||||
import { useSingleCategories } from '../hooks'
|
||||
import { useRenderI18nObject } from '@/hooks/use-i18n'
|
||||
import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list'
|
||||
import { useAppContext } from '@/context/app-context'
|
||||
import { gte } from 'semver'
|
||||
import Tooltip from '@/app/components/base/tooltip'
|
||||
import { getMarketplaceUrl } from '@/utils/var'
|
||||
|
||||
type Props = {
|
||||
className?: string
|
||||
@@ -166,7 +167,7 @@ const PluginItem: FC<Props> = ({
|
||||
}
|
||||
{source === PluginSource.marketplace
|
||||
&& <>
|
||||
<a href={`${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}${theme ? `?theme=${theme}` : ''}`} target='_blank' className='flex items-center gap-0.5'>
|
||||
<a href={getMarketplaceUrl(`/plugins/${author}/${name}`, { theme })} target='_blank' className='flex items-center gap-0.5'>
|
||||
<div className='system-2xs-medium-uppercase text-text-tertiary'>{t('plugin.from')} <span className='text-text-secondary'>marketplace</span></div>
|
||||
<RiArrowRightUpLine className='h-3 w-3 text-text-tertiary' />
|
||||
</a>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import React, { useMemo, useRef, useState } from 'react'
|
||||
'use client'
|
||||
import React, { useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { MagicBox } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
|
||||
import { FileZip } from '@/app/components/base/icons/src/vender/solid/files'
|
||||
import { Github } from '@/app/components/base/icons/src/vender/solid/general'
|
||||
@@ -14,12 +15,18 @@ import { noop } from 'lodash-es'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import Button from '@/app/components/base/button'
|
||||
|
||||
type InstallMethod = {
|
||||
icon: React.FC<{ className?: string }>
|
||||
text: string
|
||||
action: string
|
||||
}
|
||||
|
||||
const Empty = () => {
|
||||
const { t } = useTranslation()
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
const [selectedAction, setSelectedAction] = useState<string | null>(null)
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
||||
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace, plugin_installation_permission } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const setActiveTab = usePluginPageContext(v => v.setActiveTab)
|
||||
|
||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
@@ -39,6 +46,22 @@ const Empty = () => {
|
||||
return t('plugin.list.notFound')
|
||||
}, [pluginList?.plugins.length, t, filters.categories.length, filters.tags.length, filters.searchQuery])
|
||||
|
||||
const [installMethods, setInstallMethods] = useState<InstallMethod[]>([])
|
||||
useEffect(() => {
|
||||
const methods = []
|
||||
if (enable_marketplace)
|
||||
methods.push({ icon: MagicBox, text: t('plugin.source.marketplace'), action: 'marketplace' })
|
||||
|
||||
if (plugin_installation_permission.restrict_to_marketplace_only) {
|
||||
setInstallMethods(methods)
|
||||
}
|
||||
else {
|
||||
methods.push({ icon: Github, text: t('plugin.source.github'), action: 'github' })
|
||||
methods.push({ icon: FileZip, text: t('plugin.source.local'), action: 'local' })
|
||||
setInstallMethods(methods)
|
||||
}
|
||||
}, [plugin_installation_permission, enable_marketplace, t])
|
||||
|
||||
return (
|
||||
<div className='relative z-0 w-full grow'>
|
||||
{/* skeleton */}
|
||||
@@ -71,15 +94,7 @@ const Empty = () => {
|
||||
accept={SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS}
|
||||
/>
|
||||
<div className='flex w-full flex-col gap-y-1'>
|
||||
{[
|
||||
...(
|
||||
(enable_marketplace)
|
||||
? [{ icon: MagicBox, text: t('plugin.list.source.marketplace'), action: 'marketplace' }]
|
||||
: []
|
||||
),
|
||||
{ icon: Github, text: t('plugin.list.source.github'), action: 'github' },
|
||||
{ icon: FileZip, text: t('plugin.list.source.local'), action: 'local' },
|
||||
].map(({ icon: Icon, text, action }) => (
|
||||
{installMethods.map(({ icon: Icon, text, action }) => (
|
||||
<Button
|
||||
key={action}
|
||||
className='justify-start gap-x-0.5 px-3'
|
||||
|
@@ -136,7 +136,7 @@ const PluginPage = ({
|
||||
const options = usePluginPageContext(v => v.options)
|
||||
const activeTab = usePluginPageContext(v => v.activeTab)
|
||||
const setActiveTab = usePluginPageContext(v => v.setActiveTab)
|
||||
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace, branding } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
|
||||
const isPluginsTab = useMemo(() => activeTab === PLUGIN_PAGE_TABS_MAP.plugins, [activeTab])
|
||||
const isExploringMarketplace = useMemo(() => {
|
||||
@@ -225,7 +225,7 @@ const PluginPage = ({
|
||||
)
|
||||
}
|
||||
{
|
||||
canSetPermissions && (
|
||||
canSetPermissions && !branding.enabled && (
|
||||
<Tooltip
|
||||
popupContent={t('plugin.privilege.title')}
|
||||
>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { useRef, useState } from 'react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
import { RiAddLine, RiArrowDownSLine } from '@remixicon/react'
|
||||
import Button from '@/app/components/base/button'
|
||||
import { MagicBox } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices'
|
||||
@@ -22,6 +22,13 @@ import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
type Props = {
|
||||
onSwitchToMarketplaceTab: () => void
|
||||
}
|
||||
|
||||
type InstallMethod = {
|
||||
icon: React.FC<{ className?: string }>
|
||||
text: string
|
||||
action: string
|
||||
}
|
||||
|
||||
const InstallPluginDropdown = ({
|
||||
onSwitchToMarketplaceTab,
|
||||
}: Props) => {
|
||||
@@ -30,7 +37,7 @@ const InstallPluginDropdown = ({
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false)
|
||||
const [selectedAction, setSelectedAction] = useState<string | null>(null)
|
||||
const [selectedFile, setSelectedFile] = useState<File | null>(null)
|
||||
const { enable_marketplace } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
const { enable_marketplace, plugin_installation_permission } = useGlobalPublicStore(s => s.systemFeatures)
|
||||
|
||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = event.target.files?.[0]
|
||||
@@ -54,6 +61,22 @@ const InstallPluginDropdown = ({
|
||||
// console.log(res)
|
||||
// }
|
||||
|
||||
const [installMethods, setInstallMethods] = useState<InstallMethod[]>([])
|
||||
useEffect(() => {
|
||||
const methods = []
|
||||
if (enable_marketplace)
|
||||
methods.push({ icon: MagicBox, text: t('plugin.source.marketplace'), action: 'marketplace' })
|
||||
|
||||
if (plugin_installation_permission.restrict_to_marketplace_only) {
|
||||
setInstallMethods(methods)
|
||||
}
|
||||
else {
|
||||
methods.push({ icon: Github, text: t('plugin.source.github'), action: 'github' })
|
||||
methods.push({ icon: FileZip, text: t('plugin.source.local'), action: 'local' })
|
||||
setInstallMethods(methods)
|
||||
}
|
||||
}, [plugin_installation_permission, enable_marketplace, t])
|
||||
|
||||
return (
|
||||
<PortalToFollowElem
|
||||
open={isMenuOpen}
|
||||
@@ -84,15 +107,7 @@ const InstallPluginDropdown = ({
|
||||
accept={SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS}
|
||||
/>
|
||||
<div className='w-full'>
|
||||
{[
|
||||
...(
|
||||
(enable_marketplace)
|
||||
? [{ icon: MagicBox, text: t('plugin.source.marketplace'), action: 'marketplace' }]
|
||||
: []
|
||||
),
|
||||
{ icon: Github, text: t('plugin.source.github'), action: 'github' },
|
||||
{ icon: FileZip, text: t('plugin.source.local'), action: 'local' },
|
||||
].map(({ icon: Icon, text, action }) => (
|
||||
{installMethods.map(({ icon: Icon, text, action }) => (
|
||||
<div
|
||||
key={action}
|
||||
className='flex w-full !cursor-pointer items-center gap-1 rounded-lg px-2 py-1.5 hover:bg-state-base-hover'
|
||||
|
@@ -94,7 +94,11 @@ export type PluginManifestInMarket = {
|
||||
introduction: string
|
||||
verified: boolean
|
||||
install_count: number
|
||||
badges: string[]
|
||||
badges: string[],
|
||||
verification: {
|
||||
authorized_category: 'langgenius' | 'partner' | 'community'
|
||||
},
|
||||
from: Dependency['type']
|
||||
}
|
||||
|
||||
export type PluginDetail = {
|
||||
@@ -145,7 +149,11 @@ export type Plugin = {
|
||||
settings: CredentialFormSchemaBase[]
|
||||
}
|
||||
tags: { name: string }[]
|
||||
badges: string[]
|
||||
badges: string[],
|
||||
verification: {
|
||||
authorized_category: 'langgenius' | 'partner' | 'community'
|
||||
},
|
||||
from: Dependency['type']
|
||||
}
|
||||
|
||||
export enum PermissionType {
|
||||
|
@@ -12,7 +12,7 @@ import { useMarketplace } from './hooks'
|
||||
import List from '@/app/components/plugins/marketplace/list'
|
||||
import Loading from '@/app/components/base/loading'
|
||||
import { getLocaleOnClient } from '@/i18n'
|
||||
import { MARKETPLACE_URL_PREFIX } from '@/config'
|
||||
import { getMarketplaceUrl } from '@/utils/var'
|
||||
|
||||
type MarketplaceProps = {
|
||||
searchPluginText: string
|
||||
@@ -84,7 +84,7 @@ const Marketplace = ({
|
||||
</span>
|
||||
{t('common.operation.in')}
|
||||
<a
|
||||
href={`${MARKETPLACE_URL_PREFIX}?language=${locale}&q=${searchPluginText}&tags=${filterPluginTags.join(',')}${theme ? `&theme=${theme}` : ''}`}
|
||||
href={getMarketplaceUrl('', { language: locale, q: searchPluginText, tags: filterPluginTags.join(','), theme })}
|
||||
className='system-sm-medium ml-1 flex items-center text-text-accent'
|
||||
target='_blank'
|
||||
>
|
||||
|
@@ -12,9 +12,9 @@ import {
|
||||
PortalToFollowElemTrigger,
|
||||
} from '@/app/components/base/portal-to-follow-elem'
|
||||
import cn from '@/utils/classnames'
|
||||
import { MARKETPLACE_URL_PREFIX } from '@/config'
|
||||
import { useDownloadPlugin } from '@/service/use-plugins'
|
||||
import { downloadFile } from '@/utils/format'
|
||||
import { getMarketplaceUrl } from '@/utils/var'
|
||||
|
||||
type Props = {
|
||||
open: boolean
|
||||
@@ -80,7 +80,7 @@ const OperationDropdown: FC<Props> = ({
|
||||
<PortalToFollowElemContent className='z-[9999]'>
|
||||
<div className='w-[112px] rounded-xl border-[0.5px] border-components-panel-border bg-components-panel-bg-blur p-1 shadow-lg'>
|
||||
<div onClick={handleDownload} className='system-md-regular cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>{t('common.operation.download')}</div>
|
||||
<a href={`${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}${theme ? `?theme=${theme}` : ''}`} target='_blank' className='system-md-regular block cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>{t('common.operation.viewDetails')}</a>
|
||||
<a href={getMarketplaceUrl(`/plugins/${author}/${name}`, { theme })} target='_blank' className='system-md-regular block cursor-pointer rounded-lg px-3 py-1.5 text-text-secondary hover:bg-state-base-hover'>{t('common.operation.viewDetails')}</a>
|
||||
</div>
|
||||
</PortalToFollowElemContent>
|
||||
</PortalToFollowElem>
|
||||
|
@@ -1,30 +0,0 @@
|
||||
'use client'
|
||||
import type { FC } from 'react'
|
||||
import classNames from '@/utils/classnames'
|
||||
import { useGlobalPublicStore } from '@/context/global-public-context'
|
||||
import { useTheme } from 'next-themes'
|
||||
|
||||
type LoginLogoProps = {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const LoginLogo: FC<LoginLogoProps> = ({
|
||||
className,
|
||||
}) => {
|
||||
const { systemFeatures } = useGlobalPublicStore()
|
||||
const { theme } = useTheme()
|
||||
|
||||
let src = theme === 'light' ? '/logo/logo-site.png' : `/logo/logo-site-${theme}.png`
|
||||
if (systemFeatures.branding.enabled)
|
||||
src = systemFeatures.branding.login_page_logo
|
||||
|
||||
return (
|
||||
<img
|
||||
src={src}
|
||||
className={classNames('block w-auto h-10', className)}
|
||||
alt='logo'
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default LoginLogo
|
@@ -64,6 +64,8 @@ const translation = {
|
||||
skip: 'Skip',
|
||||
format: 'Format',
|
||||
more: 'More',
|
||||
selectAll: 'Select All',
|
||||
deSelectAll: 'Deselect All',
|
||||
},
|
||||
errorMsg: {
|
||||
fieldRequired: '{{field}} is required',
|
||||
|
@@ -154,6 +154,7 @@ const translation = {
|
||||
next: 'Next',
|
||||
pluginLoadError: 'Plugin load error',
|
||||
pluginLoadErrorDesc: 'This plugin will not be installed',
|
||||
installWarning: 'This plugin is not allowed to be installed.',
|
||||
},
|
||||
installFromGitHub: {
|
||||
installPlugin: 'Install plugin from GitHub',
|
||||
|
@@ -64,6 +64,8 @@ const translation = {
|
||||
in: '中',
|
||||
format: 'フォーマット',
|
||||
more: 'もっと',
|
||||
selectAll: 'すべて選択',
|
||||
deSelectAll: 'すべて選択解除',
|
||||
},
|
||||
errorMsg: {
|
||||
fieldRequired: '{{field}}は必要です',
|
||||
|
@@ -137,6 +137,7 @@ const translation = {
|
||||
installPlugin: 'プラグインをインストールする',
|
||||
back: '戻る',
|
||||
uploadingPackage: '{{packageName}}をアップロード中...',
|
||||
installWarning: 'このプラグインはインストールを許可されていません。',
|
||||
},
|
||||
installFromGitHub: {
|
||||
installedSuccessfully: 'インストールに成功しました',
|
||||
|
@@ -64,6 +64,8 @@ const translation = {
|
||||
skip: '跳过',
|
||||
format: '格式化',
|
||||
more: '更多',
|
||||
selectAll: '全选',
|
||||
deSelectAll: '取消全选',
|
||||
},
|
||||
errorMsg: {
|
||||
fieldRequired: '{{field}} 为必填项',
|
||||
|
@@ -154,6 +154,7 @@ const translation = {
|
||||
next: '下一步',
|
||||
pluginLoadError: '插件加载错误',
|
||||
pluginLoadErrorDesc: '此插件将不会被安装',
|
||||
installWarning: '此插件不允许安装。',
|
||||
},
|
||||
installFromGitHub: {
|
||||
installPlugin: '从 GitHub 安装插件',
|
||||
|
@@ -13,12 +13,23 @@ export enum LicenseStatus {
|
||||
LOST = 'lost',
|
||||
}
|
||||
|
||||
export enum InstallationScope {
|
||||
ALL = 'all',
|
||||
NONE = 'none',
|
||||
OFFICIAL_ONLY = 'official_only',
|
||||
OFFICIAL_AND_PARTNER = 'official_and_specific_partners',
|
||||
}
|
||||
|
||||
type License = {
|
||||
status: LicenseStatus
|
||||
expired_at: string | null
|
||||
}
|
||||
|
||||
export type SystemFeatures = {
|
||||
plugin_installation_permission: {
|
||||
plugin_installation_scope: InstallationScope,
|
||||
restrict_to_marketplace_only: boolean
|
||||
},
|
||||
sso_enforced_for_signin: boolean
|
||||
sso_enforced_for_signin_protocol: SSOProtocol | ''
|
||||
sso_enforced_for_web: boolean
|
||||
@@ -50,6 +61,10 @@ export type SystemFeatures = {
|
||||
}
|
||||
|
||||
export const defaultSystemFeatures: SystemFeatures = {
|
||||
plugin_installation_permission: {
|
||||
plugin_installation_scope: InstallationScope.ALL,
|
||||
restrict_to_marketplace_only: false,
|
||||
},
|
||||
sso_enforced_for_signin: false,
|
||||
sso_enforced_for_signin_protocol: '',
|
||||
sso_enforced_for_web: false,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { MAX_VAR_KEY_LENGTH, VAR_ITEM_TEMPLATE, VAR_ITEM_TEMPLATE_IN_WORKFLOW, getMaxVarNameLength } from '@/config'
|
||||
import { MARKETPLACE_URL_PREFIX, MAX_VAR_KEY_LENGTH, VAR_ITEM_TEMPLATE, VAR_ITEM_TEMPLATE_IN_WORKFLOW, getMaxVarNameLength } from '@/config'
|
||||
import {
|
||||
CONTEXT_PLACEHOLDER_TEXT,
|
||||
HISTORY_PLACEHOLDER_TEXT,
|
||||
@@ -108,3 +108,15 @@ export const getVars = (value: string) => {
|
||||
// Set the value of basePath
|
||||
// example: /dify
|
||||
export const basePath = ''
|
||||
|
||||
export function getMarketplaceUrl(path: string, params?: Record<string, string | undefined>) {
|
||||
const searchParams = new URLSearchParams({ source: encodeURIComponent(window.location.origin) })
|
||||
if (params) {
|
||||
Object.keys(params).forEach((key) => {
|
||||
const value = params[key]
|
||||
if (value !== undefined && value !== null)
|
||||
searchParams.append(key, value)
|
||||
})
|
||||
}
|
||||
return `${MARKETPLACE_URL_PREFIX}${path}?${searchParams.toString()}`
|
||||
}
|
||||
|
Reference in New Issue
Block a user