| import ProviderCard from '@/app/components/plugins/provider-card' | import ProviderCard from '@/app/components/plugins/provider-card' | ||||
| import List from '@/app/components/plugins/marketplace/list' | import List from '@/app/components/plugins/marketplace/list' | ||||
| import type { Plugin } from '@/app/components/plugins/types' | import type { Plugin } from '@/app/components/plugins/types' | ||||
| import { MARKETPLACE_URL_PREFIX } from '@/config' | |||||
| import cn from '@/utils/classnames' | import cn from '@/utils/classnames' | ||||
| import { getLocaleOnClient } from '@/i18n' | import { getLocaleOnClient } from '@/i18n' | ||||
| import { getMarketplaceUrl } from '@/utils/var' | |||||
| type InstallFromMarketplaceProps = { | type InstallFromMarketplaceProps = { | ||||
| providers: ModelProvider[] | providers: ModelProvider[] | ||||
| </div> | </div> | ||||
| <div className='mb-2 flex items-center pt-2'> | <div className='mb-2 flex items-center pt-2'> | ||||
| <span className='system-sm-regular pr-1 text-text-tertiary'>{t('common.modelProvider.discoverMore')}</span> | <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')} | {t('plugin.marketplace.difyMarketplace')} | ||||
| <RiArrowRightUpLine className='h-4 w-4' /> | <RiArrowRightUpLine className='h-4 w-4' /> | ||||
| </Link> | </Link> |
| import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks' | import { useMixedTranslation } from '@/app/components/plugins/marketplace/hooks' | ||||
| import Partner from '../base/badges/partner' | import Partner from '../base/badges/partner' | ||||
| import Verified from '../base/badges/verified' | import Verified from '../base/badges/verified' | ||||
| import { RiAlertFill } from '@remixicon/react' | |||||
| export type Props = { | export type Props = { | ||||
| className?: string | className?: string | ||||
| isLoading?: boolean | isLoading?: boolean | ||||
| loadingFileName?: string | loadingFileName?: string | ||||
| locale?: string | locale?: string | ||||
| limitedInstall?: boolean | |||||
| } | } | ||||
| const Card = ({ | const Card = ({ | ||||
| isLoading = false, | isLoading = false, | ||||
| loadingFileName, | loadingFileName, | ||||
| locale: localeFromProps, | locale: localeFromProps, | ||||
| limitedInstall = false, | |||||
| }: Props) => { | }: Props) => { | ||||
| const defaultLocale = useGetLanguage() | const defaultLocale = useGetLanguage() | ||||
| const locale = localeFromProps ? getLanguage(localeFromProps) : defaultLocale | const locale = localeFromProps ? getLanguage(localeFromProps) : defaultLocale | ||||
| obj ? renderI18nObject(obj, locale) : '' | obj ? renderI18nObject(obj, locale) : '' | ||||
| const isPartner = badges.includes('partner') | 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) { | if (isLoading) { | ||||
| return ( | return ( | ||||
| <Placeholder | <Placeholder | ||||
| return ( | return ( | ||||
| <div className={wrapClassName}> | <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> | </div> | ||||
| <OrgInfo | |||||
| className="mt-0.5" | |||||
| orgName={org} | |||||
| packageName={name} | |||||
| /> | |||||
| </div> | </div> | ||||
| <Description | |||||
| className="mt-3" | |||||
| text={getLocalizedText(brief)} | |||||
| descriptionLineRows={descriptionLineRows} | |||||
| /> | |||||
| {footer && <div>{footer}</div>} | |||||
| </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> | </div> | ||||
| ) | ) | ||||
| } | } |
| 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) | |||||
| } |
| plugin_id: data.unique_identifier, | plugin_id: data.unique_identifier, | ||||
| } | } | ||||
| onFetchedPayload(payload) | onFetchedPayload(payload) | ||||
| setPayload(payload) | |||||
| setPayload({ ...payload, from: dependency.type }) | |||||
| } | } | ||||
| // eslint-disable-next-line react-hooks/exhaustive-deps | // eslint-disable-next-line react-hooks/exhaustive-deps | ||||
| }, [data]) | }, [data]) |
| import { MARKETPLACE_API_PREFIX } from '@/config' | import { MARKETPLACE_API_PREFIX } from '@/config' | ||||
| import Version from '../../base/version' | import Version from '../../base/version' | ||||
| import type { VersionProps } from '../../../types' | import type { VersionProps } from '../../../types' | ||||
| import usePluginInstallLimit from '../../hooks/use-install-plugin-limit' | |||||
| type Props = { | type Props = { | ||||
| checked: boolean | checked: boolean | ||||
| ...particleVersionInfo, | ...particleVersionInfo, | ||||
| toInstallVersion: payload.version, | toInstallVersion: payload.version, | ||||
| } | } | ||||
| const { canInstall } = usePluginInstallLimit(payload) | |||||
| return ( | return ( | ||||
| <div className='flex items-center space-x-2'> | <div className='flex items-center space-x-2'> | ||||
| <Checkbox | <Checkbox | ||||
| disabled={!canInstall} | |||||
| className='shrink-0' | className='shrink-0' | ||||
| checked={checked} | checked={checked} | ||||
| onCheck={() => onCheckedChange(payload)} | onCheck={() => onCheckedChange(payload)} | ||||
| icon: isFromMarketPlace ? `${MARKETPLACE_API_PREFIX}/plugins/${payload.org}/${payload.name}/icon` : getIconUrl(payload.icon), | icon: isFromMarketPlace ? `${MARKETPLACE_API_PREFIX}/plugins/${payload.org}/${payload.name}/icon` : getIconUrl(payload.icon), | ||||
| }} | }} | ||||
| titleLeft={payload.version ? <Version {...versionInfo} /> : null} | titleLeft={payload.version ? <Version {...versionInfo} /> : null} | ||||
| limitedInstall={!canInstall} | |||||
| /> | /> | ||||
| </div> | </div> | ||||
| ) | ) |
| const plugin = pluginManifestToCardPluginProps(payload.value.manifest) | const plugin = pluginManifestToCardPluginProps(payload.value.manifest) | ||||
| return ( | return ( | ||||
| <LoadedItem | <LoadedItem | ||||
| payload={plugin} | |||||
| payload={{ ...plugin, from: payload.type }} | |||||
| checked={checked} | checked={checked} | ||||
| onCheckedChange={onCheckedChange} | onCheckedChange={onCheckedChange} | ||||
| isFromMarketPlace={isFromMarketPlace} | isFromMarketPlace={isFromMarketPlace} |
| 'use client' | '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 React, { useCallback, useEffect, useMemo, useState } from 'react' | ||||
| import type { Dependency, GitHubItemAndMarketPlaceDependency, PackageDependency, Plugin, VersionInfo } from '../../../types' | import type { Dependency, GitHubItemAndMarketPlaceDependency, PackageDependency, Plugin, VersionInfo } from '../../../types' | ||||
| import MarketplaceItem from '../item/marketplace-item' | import MarketplaceItem from '../item/marketplace-item' | ||||
| import produce from 'immer' | import produce from 'immer' | ||||
| import PackageItem from '../item/package-item' | import PackageItem from '../item/package-item' | ||||
| import LoadingError from '../../base/loading-error' | import LoadingError from '../../base/loading-error' | ||||
| import { useGlobalPublicStore } from '@/context/global-public-context' | |||||
| import { pluginInstallLimit } from '../../hooks/use-install-plugin-limit' | |||||
| type Props = { | type Props = { | ||||
| allPlugins: Dependency[] | allPlugins: Dependency[] | ||||
| selectedPlugins: Plugin[] | 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 | onLoadedAllPlugin: (installedInfo: Record<string, VersionInfo>) => void | ||||
| isFromMarketPlace?: boolean | isFromMarketPlace?: boolean | ||||
| } | } | ||||
| const InstallByDSLList: FC<Props> = ({ | |||||
| export type ExposeRefs = { | |||||
| selectAllPlugins: () => void | |||||
| deSelectAllPlugins: () => void | |||||
| } | |||||
| const InstallByDSLList: ForwardRefRenderFunction<ExposeRefs, Props> = ({ | |||||
| allPlugins, | allPlugins, | ||||
| selectedPlugins, | selectedPlugins, | ||||
| onSelect, | onSelect, | ||||
| onSelectAll, | |||||
| onDeSelectAll, | |||||
| onLoadedAllPlugin, | onLoadedAllPlugin, | ||||
| isFromMarketPlace, | isFromMarketPlace, | ||||
| }) => { | |||||
| }, ref) => { | |||||
| const systemFeatures = useGlobalPublicStore(s => s.systemFeatures) | |||||
| // DSL has id, to get plugin info to show more info | // 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 { isLoading: isFetchingMarketplaceDataById, data: infoGetById, error: infoByIdError } = useFetchPluginsInMarketPlaceByInfo(allPlugins.filter(d => d.type === 'marketplace').map((d) => { | ||||
| const dependecy = (d as GitHubItemAndMarketPlaceDependency).value | const dependecy = (d as GitHubItemAndMarketPlaceDependency).value | ||||
| const sortedList = allPlugins.filter(d => d.type === 'marketplace').map((d) => { | const sortedList = allPlugins.filter(d => d.type === 'marketplace').map((d) => { | ||||
| const p = d as GitHubItemAndMarketPlaceDependency | const p = d as GitHubItemAndMarketPlaceDependency | ||||
| const id = p.value.marketplace_plugin_unique_identifier?.split(':')[0] | 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 payloads = sortedList | ||||
| const failedIndex: number[] = [] | const failedIndex: number[] = [] | ||||
| if (payloads[i]) { | if (payloads[i]) { | ||||
| draft[index] = { | draft[index] = { | ||||
| ...payloads[i], | ...payloads[i], | ||||
| version: payloads[i].version || payloads[i].latest_version, | |||||
| version: payloads[i]!.version || payloads[i]!.latest_version, | |||||
| } | } | ||||
| } | } | ||||
| else { failedIndex.push(index) } | else { failedIndex.push(index) } | ||||
| const handleSelect = useCallback((index: number) => { | const handleSelect = useCallback((index: number) => { | ||||
| return () => { | 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 ( | return ( | ||||
| <> | <> | ||||
| {allPlugins.map((d, index) => { | {allPlugins.map((d, index) => { | ||||
| key={index} | key={index} | ||||
| checked={!!selectedPlugins.find(p => p.plugin_id === plugins[index]?.plugin_id)} | checked={!!selectedPlugins.find(p => p.plugin_id === plugins[index]?.plugin_id)} | ||||
| onCheckedChange={handleSelect(index)} | onCheckedChange={handleSelect(index)} | ||||
| payload={plugin} | |||||
| payload={{ ...plugin, from: d.type } as Plugin} | |||||
| version={(d as GitHubItemAndMarketPlaceDependency).value.version! || plugin?.version || ''} | version={(d as GitHubItemAndMarketPlaceDependency).value.version! || plugin?.version || ''} | ||||
| versionInfo={getVersionInfo(`${plugin?.org || plugin?.author}/${plugin?.name}`)} | versionInfo={getVersionInfo(`${plugin?.org || plugin?.author}/${plugin?.name}`)} | ||||
| /> | /> | ||||
| </> | </> | ||||
| ) | ) | ||||
| } | } | ||||
| export default React.memo(InstallByDSLList) | |||||
| export default React.forwardRef(InstallByDSLList) |
| 'use client' | 'use client' | ||||
| import type { FC } from 'react' | import type { FC } from 'react' | ||||
| import { useRef } from 'react' | |||||
| import React, { useCallback, useState } from 'react' | import React, { useCallback, useState } from 'react' | ||||
| import type { Dependency, InstallStatusResponse, Plugin, VersionInfo } from '../../../types' | import type { Dependency, InstallStatusResponse, Plugin, VersionInfo } from '../../../types' | ||||
| import Button from '@/app/components/base/button' | import Button from '@/app/components/base/button' | ||||
| import { RiLoader2Line } from '@remixicon/react' | import { RiLoader2Line } from '@remixicon/react' | ||||
| import { useTranslation } from 'react-i18next' | import { useTranslation } from 'react-i18next' | ||||
| import type { ExposeRefs } from './install-multi' | |||||
| import InstallMulti from './install-multi' | import InstallMulti from './install-multi' | ||||
| import { useInstallOrUpdate } from '@/service/use-plugins' | import { useInstallOrUpdate } from '@/service/use-plugins' | ||||
| import useRefreshPluginList from '../../hooks/use-refresh-plugin-list' | import useRefreshPluginList from '../../hooks/use-refresh-plugin-list' | ||||
| import { useCanInstallPluginFromMarketplace } from '@/app/components/plugins/plugin-page/use-permission' | import { useCanInstallPluginFromMarketplace } from '@/app/components/plugins/plugin-page/use-permission' | ||||
| import { useMittContextSelector } from '@/context/mitt-context' | import { useMittContextSelector } from '@/context/mitt-context' | ||||
| import Checkbox from '@/app/components/base/checkbox' | |||||
| const i18nPrefix = 'plugin.installModal' | const i18nPrefix = 'plugin.installModal' | ||||
| type Props = { | type Props = { | ||||
| const [selectedPlugins, setSelectedPlugins] = React.useState<Plugin[]>([]) | const [selectedPlugins, setSelectedPlugins] = React.useState<Plugin[]>([]) | ||||
| const [selectedIndexes, setSelectedIndexes] = React.useState<number[]>([]) | const [selectedIndexes, setSelectedIndexes] = React.useState<number[]>([]) | ||||
| const selectedPluginsNum = selectedPlugins.length | const selectedPluginsNum = selectedPlugins.length | ||||
| const installMultiRef = useRef<ExposeRefs>(null) | |||||
| const { refreshPluginList } = useRefreshPluginList() | 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 [canInstall, setCanInstall] = React.useState(false) | ||||
| const [installedInfo, setInstalledInfo] = useState<Record<string, VersionInfo> | undefined>(undefined) | const [installedInfo, setInstalledInfo] = useState<Record<string, VersionInfo> | undefined>(undefined) | ||||
| installedInfo: installedInfo!, | 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() | const { canInstallPluginFromMarketplace } = useCanInstallPluginFromMarketplace() | ||||
| return ( | return ( | ||||
| <> | <> | ||||
| </div> | </div> | ||||
| <div className='w-full space-y-1 rounded-2xl bg-background-section-burn p-2'> | <div className='w-full space-y-1 rounded-2xl bg-background-section-burn p-2'> | ||||
| <InstallMulti | <InstallMulti | ||||
| ref={installMultiRef} | |||||
| allPlugins={allPlugins} | allPlugins={allPlugins} | ||||
| selectedPlugins={selectedPlugins} | selectedPlugins={selectedPlugins} | ||||
| onSelect={handleSelect} | onSelect={handleSelect} | ||||
| onSelectAll={handleSelectAll} | |||||
| onDeSelectAll={handleDeSelectAll} | |||||
| onLoadedAllPlugin={handleLoadedAllPlugin} | onLoadedAllPlugin={handleLoadedAllPlugin} | ||||
| isFromMarketPlace={isFromMarketPlace} | isFromMarketPlace={isFromMarketPlace} | ||||
| /> | /> | ||||
| </div> | </div> | ||||
| {/* Action Buttons */} | {/* Action Buttons */} | ||||
| {!isHideButton && ( | {!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> | ||||
| )} | |||||
| <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> | </div> | ||||
| )} | )} | ||||
| /> | /> | ||||
| </p> | </p> | ||||
| {!isDifyVersionCompatible && ( | {!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 })} | {t('plugin.difyVersionNotCompatible', { minimalDifyVersion: payload.meta.minimum_dify_version })} | ||||
| </p> | </p> | ||||
| )} | )} |
| import { usePluginTaskList } from '@/service/use-plugins' | import { usePluginTaskList } from '@/service/use-plugins' | ||||
| import { gte } from 'semver' | import { gte } from 'semver' | ||||
| import { useAppContext } from '@/context/app-context' | import { useAppContext } from '@/context/app-context' | ||||
| import useInstallPluginLimit from '../../hooks/use-install-plugin-limit' | |||||
| const i18nPrefix = 'plugin.installModal' | const i18nPrefix = 'plugin.installModal' | ||||
| const isDifyVersionCompatible = useMemo(() => { | const isDifyVersionCompatible = useMemo(() => { | ||||
| if (!pluginDeclaration || !langeniusVersionInfo.current_version) return true | if (!pluginDeclaration || !langeniusVersionInfo.current_version) return true | ||||
| return gte(langeniusVersionInfo.current_version, pluginDeclaration?.manifest.meta.minimum_dify_version ?? '0.0.0') | 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 ( | return ( | ||||
| <> | <> | ||||
| <div className='flex flex-col items-start justify-center gap-4 self-stretch px-6 py-3'> | <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'> | <div className='system-md-regular text-text-secondary'> | ||||
| <p>{t(`${i18nPrefix}.readyToInstall`)}</p> | <p>{t(`${i18nPrefix}.readyToInstall`)}</p> | ||||
| {!isDifyVersionCompatible && ( | {!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 })} | {t('plugin.difyVersionNotCompatible', { minimalDifyVersion: pluginDeclaration?.manifest.meta.minimum_dify_version })} | ||||
| </p> | </p> | ||||
| )} | )} | ||||
| installedVersion={installedVersion} | installedVersion={installedVersion} | ||||
| toInstallVersion={toInstallVersion} | toInstallVersion={toInstallVersion} | ||||
| />} | />} | ||||
| limitedInstall={!canInstall} | |||||
| /> | /> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| <Button | <Button | ||||
| variant='primary' | variant='primary' | ||||
| className='flex min-w-[72px] space-x-0.5' | className='flex min-w-[72px] space-x-0.5' | ||||
| disabled={isInstalling || isLoading} | |||||
| disabled={isInstalling || isLoading || !canInstall} | |||||
| onClick={handleInstall} | onClick={handleInstall} | ||||
| > | > | ||||
| {isInstalling && <RiLoader2Line className='h-4 w-4 animate-spin-slow' />} | {isInstalling && <RiLoader2Line className='h-4 w-4 animate-spin-slow' />} |
| import type { Plugin, PluginDeclaration, PluginManifestInMarket } from '../types' | import type { Plugin, PluginDeclaration, PluginManifestInMarket } from '../types' | ||||
| import type { GitHubUrlInfo } from '@/app/components/plugins/types' | import type { GitHubUrlInfo } from '@/app/components/plugins/types' | ||||
| import { isEmpty } from 'lodash-es' | |||||
| export const pluginManifestToCardPluginProps = (pluginManifest: PluginDeclaration): Plugin => { | export const pluginManifestToCardPluginProps = (pluginManifest: PluginDeclaration): Plugin => { | ||||
| return { | return { | ||||
| }, | }, | ||||
| tags: [], | tags: [], | ||||
| badges: pluginManifest.badges, | badges: pluginManifest.badges, | ||||
| verification: isEmpty(pluginManifest.verification) ? { authorized_category: 'langgenius' } : pluginManifest.verification, | |||||
| } | } | ||||
| } | } | ||||
| > | > | ||||
| {t('plugin.detailPanel.operation.install')} | {t('plugin.detailPanel.operation.install')} | ||||
| </Button> | </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 | <Button | ||||
| className='w-full gap-0.5' | className='w-full gap-0.5' | ||||
| > | > |
| } from '@/app/components/plugins/marketplace/types' | } from '@/app/components/plugins/marketplace/types' | ||||
| import { | import { | ||||
| MARKETPLACE_API_PREFIX, | MARKETPLACE_API_PREFIX, | ||||
| MARKETPLACE_URL_PREFIX, | |||||
| } from '@/config' | } from '@/config' | ||||
| import { getMarketplaceUrl } from '@/utils/var' | |||||
| export const getPluginIconInMarketplace = (plugin: Plugin) => { | export const getPluginIconInMarketplace = (plugin: Plugin) => { | ||||
| if (plugin.type === 'bundle') | if (plugin.type === 'bundle') | ||||
| } | } | ||||
| } | } | ||||
| export const getPluginLinkInMarketplace = (plugin: Plugin) => { | |||||
| export const getPluginLinkInMarketplace = (plugin: Plugin, params?: Record<string, string | undefined>) => { | |||||
| if (plugin.type === 'bundle') | 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) => { | export const getMarketplacePluginsByCollectionId = async (collectionId: string, query?: CollectionsAndPluginsSearchParams) => { |
| import { useModalContext } from '@/context/modal-context' | import { useModalContext } from '@/context/modal-context' | ||||
| import { useProviderContext } from '@/context/provider-context' | import { useProviderContext } from '@/context/provider-context' | ||||
| import { useInvalidateAllToolProviders } from '@/service/use-tools' | 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 cn from '@/utils/classnames' | ||||
| import { getMarketplaceUrl } from '@/utils/var' | |||||
| const i18nPrefix = 'plugin.action' | const i18nPrefix = 'plugin.action' | ||||
| if (isFromGitHub) | if (isFromGitHub) | ||||
| return `https://github.com/${meta!.repo}` | return `https://github.com/${meta!.repo}` | ||||
| if (isFromMarketplace) | if (isFromMarketplace) | ||||
| return `${MARKETPLACE_URL_PREFIX}/plugins/${author}/${name}${theme ? `?theme=${theme}` : ''}` | |||||
| return getMarketplaceUrl(`/plugins/${author}/${name}`, { theme }) | |||||
| return '' | return '' | ||||
| }, [author, isFromGitHub, isFromMarketplace, meta, name, theme]) | }, [author, isFromGitHub, isFromMarketplace, meta, name, theme]) | ||||
| import Title from '../card/base/title' | import Title from '../card/base/title' | ||||
| import Action from './action' | import Action from './action' | ||||
| import cn from '@/utils/classnames' | import cn from '@/utils/classnames' | ||||
| import { API_PREFIX, MARKETPLACE_URL_PREFIX } from '@/config' | |||||
| import { API_PREFIX } from '@/config' | |||||
| import { useSingleCategories } from '../hooks' | import { useSingleCategories } from '../hooks' | ||||
| import { useRenderI18nObject } from '@/hooks/use-i18n' | import { useRenderI18nObject } from '@/hooks/use-i18n' | ||||
| import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list' | import useRefreshPluginList from '@/app/components/plugins/install-plugin/hooks/use-refresh-plugin-list' | ||||
| import { useAppContext } from '@/context/app-context' | import { useAppContext } from '@/context/app-context' | ||||
| import { gte } from 'semver' | import { gte } from 'semver' | ||||
| import Tooltip from '@/app/components/base/tooltip' | import Tooltip from '@/app/components/base/tooltip' | ||||
| import { getMarketplaceUrl } from '@/utils/var' | |||||
| type Props = { | type Props = { | ||||
| className?: string | className?: string | ||||
| } | } | ||||
| {source === PluginSource.marketplace | {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> | <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' /> | <RiArrowRightUpLine className='h-3 w-3 text-text-tertiary' /> | ||||
| </a> | </a> |
| 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 { MagicBox } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' | ||||
| import { FileZip } from '@/app/components/base/icons/src/vender/solid/files' | import { FileZip } from '@/app/components/base/icons/src/vender/solid/files' | ||||
| import { Github } from '@/app/components/base/icons/src/vender/solid/general' | import { Github } from '@/app/components/base/icons/src/vender/solid/general' | ||||
| import { useGlobalPublicStore } from '@/context/global-public-context' | import { useGlobalPublicStore } from '@/context/global-public-context' | ||||
| import Button from '@/app/components/base/button' | import Button from '@/app/components/base/button' | ||||
| type InstallMethod = { | |||||
| icon: React.FC<{ className?: string }> | |||||
| text: string | |||||
| action: string | |||||
| } | |||||
| const Empty = () => { | const Empty = () => { | ||||
| const { t } = useTranslation() | const { t } = useTranslation() | ||||
| const fileInputRef = useRef<HTMLInputElement>(null) | const fileInputRef = useRef<HTMLInputElement>(null) | ||||
| const [selectedAction, setSelectedAction] = useState<string | null>(null) | const [selectedAction, setSelectedAction] = useState<string | null>(null) | ||||
| const [selectedFile, setSelectedFile] = useState<File | 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 setActiveTab = usePluginPageContext(v => v.setActiveTab) | ||||
| const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { | const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||||
| return t('plugin.list.notFound') | return t('plugin.list.notFound') | ||||
| }, [pluginList?.plugins.length, t, filters.categories.length, filters.tags.length, filters.searchQuery]) | }, [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 ( | return ( | ||||
| <div className='relative z-0 w-full grow'> | <div className='relative z-0 w-full grow'> | ||||
| {/* skeleton */} | {/* skeleton */} | ||||
| accept={SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS} | accept={SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS} | ||||
| /> | /> | ||||
| <div className='flex w-full flex-col gap-y-1'> | <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 | <Button | ||||
| key={action} | key={action} | ||||
| className='justify-start gap-x-0.5 px-3' | className='justify-start gap-x-0.5 px-3' |
| const options = usePluginPageContext(v => v.options) | const options = usePluginPageContext(v => v.options) | ||||
| const activeTab = usePluginPageContext(v => v.activeTab) | const activeTab = usePluginPageContext(v => v.activeTab) | ||||
| const setActiveTab = usePluginPageContext(v => v.setActiveTab) | 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 isPluginsTab = useMemo(() => activeTab === PLUGIN_PAGE_TABS_MAP.plugins, [activeTab]) | ||||
| const isExploringMarketplace = useMemo(() => { | const isExploringMarketplace = useMemo(() => { | ||||
| ) | ) | ||||
| } | } | ||||
| { | { | ||||
| canSetPermissions && ( | |||||
| canSetPermissions && !branding.enabled && ( | |||||
| <Tooltip | <Tooltip | ||||
| popupContent={t('plugin.privilege.title')} | popupContent={t('plugin.privilege.title')} | ||||
| > | > |
| 'use client' | 'use client' | ||||
| import { useRef, useState } from 'react' | |||||
| import { useEffect, useRef, useState } from 'react' | |||||
| import { RiAddLine, RiArrowDownSLine } from '@remixicon/react' | import { RiAddLine, RiArrowDownSLine } from '@remixicon/react' | ||||
| import Button from '@/app/components/base/button' | import Button from '@/app/components/base/button' | ||||
| import { MagicBox } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' | import { MagicBox } from '@/app/components/base/icons/src/vender/solid/mediaAndDevices' | ||||
| type Props = { | type Props = { | ||||
| onSwitchToMarketplaceTab: () => void | onSwitchToMarketplaceTab: () => void | ||||
| } | } | ||||
| type InstallMethod = { | |||||
| icon: React.FC<{ className?: string }> | |||||
| text: string | |||||
| action: string | |||||
| } | |||||
| const InstallPluginDropdown = ({ | const InstallPluginDropdown = ({ | ||||
| onSwitchToMarketplaceTab, | onSwitchToMarketplaceTab, | ||||
| }: Props) => { | }: Props) => { | ||||
| const [isMenuOpen, setIsMenuOpen] = useState(false) | const [isMenuOpen, setIsMenuOpen] = useState(false) | ||||
| const [selectedAction, setSelectedAction] = useState<string | null>(null) | const [selectedAction, setSelectedAction] = useState<string | null>(null) | ||||
| const [selectedFile, setSelectedFile] = useState<File | 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 handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => { | ||||
| const file = event.target.files?.[0] | const file = event.target.files?.[0] | ||||
| // console.log(res) | // 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 ( | return ( | ||||
| <PortalToFollowElem | <PortalToFollowElem | ||||
| open={isMenuOpen} | open={isMenuOpen} | ||||
| accept={SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS} | accept={SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS} | ||||
| /> | /> | ||||
| <div className='w-full'> | <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 | <div | ||||
| key={action} | key={action} | ||||
| className='flex w-full !cursor-pointer items-center gap-1 rounded-lg px-2 py-1.5 hover:bg-state-base-hover' | className='flex w-full !cursor-pointer items-center gap-1 rounded-lg px-2 py-1.5 hover:bg-state-base-hover' |
| introduction: string | introduction: string | ||||
| verified: boolean | verified: boolean | ||||
| install_count: number | install_count: number | ||||
| badges: string[] | |||||
| badges: string[], | |||||
| verification: { | |||||
| authorized_category: 'langgenius' | 'partner' | 'community' | |||||
| }, | |||||
| from: Dependency['type'] | |||||
| } | } | ||||
| export type PluginDetail = { | export type PluginDetail = { | ||||
| settings: CredentialFormSchemaBase[] | settings: CredentialFormSchemaBase[] | ||||
| } | } | ||||
| tags: { name: string }[] | tags: { name: string }[] | ||||
| badges: string[] | |||||
| badges: string[], | |||||
| verification: { | |||||
| authorized_category: 'langgenius' | 'partner' | 'community' | |||||
| }, | |||||
| from: Dependency['type'] | |||||
| } | } | ||||
| export enum PermissionType { | export enum PermissionType { |
| import List from '@/app/components/plugins/marketplace/list' | import List from '@/app/components/plugins/marketplace/list' | ||||
| import Loading from '@/app/components/base/loading' | import Loading from '@/app/components/base/loading' | ||||
| import { getLocaleOnClient } from '@/i18n' | import { getLocaleOnClient } from '@/i18n' | ||||
| import { MARKETPLACE_URL_PREFIX } from '@/config' | |||||
| import { getMarketplaceUrl } from '@/utils/var' | |||||
| type MarketplaceProps = { | type MarketplaceProps = { | ||||
| searchPluginText: string | searchPluginText: string | ||||
| </span> | </span> | ||||
| {t('common.operation.in')} | {t('common.operation.in')} | ||||
| <a | <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' | className='system-sm-medium ml-1 flex items-center text-text-accent' | ||||
| target='_blank' | target='_blank' | ||||
| > | > |
| PortalToFollowElemTrigger, | PortalToFollowElemTrigger, | ||||
| } from '@/app/components/base/portal-to-follow-elem' | } from '@/app/components/base/portal-to-follow-elem' | ||||
| import cn from '@/utils/classnames' | import cn from '@/utils/classnames' | ||||
| import { MARKETPLACE_URL_PREFIX } from '@/config' | |||||
| import { useDownloadPlugin } from '@/service/use-plugins' | import { useDownloadPlugin } from '@/service/use-plugins' | ||||
| import { downloadFile } from '@/utils/format' | import { downloadFile } from '@/utils/format' | ||||
| import { getMarketplaceUrl } from '@/utils/var' | |||||
| type Props = { | type Props = { | ||||
| open: boolean | open: boolean | ||||
| <PortalToFollowElemContent className='z-[9999]'> | <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 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> | <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> | </div> | ||||
| </PortalToFollowElemContent> | </PortalToFollowElemContent> | ||||
| </PortalToFollowElem> | </PortalToFollowElem> |
| '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 |
| skip: 'Skip', | skip: 'Skip', | ||||
| format: 'Format', | format: 'Format', | ||||
| more: 'More', | more: 'More', | ||||
| selectAll: 'Select All', | |||||
| deSelectAll: 'Deselect All', | |||||
| }, | }, | ||||
| errorMsg: { | errorMsg: { | ||||
| fieldRequired: '{{field}} is required', | fieldRequired: '{{field}} is required', |
| next: 'Next', | next: 'Next', | ||||
| pluginLoadError: 'Plugin load error', | pluginLoadError: 'Plugin load error', | ||||
| pluginLoadErrorDesc: 'This plugin will not be installed', | pluginLoadErrorDesc: 'This plugin will not be installed', | ||||
| installWarning: 'This plugin is not allowed to be installed.', | |||||
| }, | }, | ||||
| installFromGitHub: { | installFromGitHub: { | ||||
| installPlugin: 'Install plugin from GitHub', | installPlugin: 'Install plugin from GitHub', |
| in: '中', | in: '中', | ||||
| format: 'フォーマット', | format: 'フォーマット', | ||||
| more: 'もっと', | more: 'もっと', | ||||
| selectAll: 'すべて選択', | |||||
| deSelectAll: 'すべて選択解除', | |||||
| }, | }, | ||||
| errorMsg: { | errorMsg: { | ||||
| fieldRequired: '{{field}}は必要です', | fieldRequired: '{{field}}は必要です', |
| installPlugin: 'プラグインをインストールする', | installPlugin: 'プラグインをインストールする', | ||||
| back: '戻る', | back: '戻る', | ||||
| uploadingPackage: '{{packageName}}をアップロード中...', | uploadingPackage: '{{packageName}}をアップロード中...', | ||||
| installWarning: 'このプラグインはインストールを許可されていません。', | |||||
| }, | }, | ||||
| installFromGitHub: { | installFromGitHub: { | ||||
| installedSuccessfully: 'インストールに成功しました', | installedSuccessfully: 'インストールに成功しました', |
| skip: '跳过', | skip: '跳过', | ||||
| format: '格式化', | format: '格式化', | ||||
| more: '更多', | more: '更多', | ||||
| selectAll: '全选', | |||||
| deSelectAll: '取消全选', | |||||
| }, | }, | ||||
| errorMsg: { | errorMsg: { | ||||
| fieldRequired: '{{field}} 为必填项', | fieldRequired: '{{field}} 为必填项', |
| next: '下一步', | next: '下一步', | ||||
| pluginLoadError: '插件加载错误', | pluginLoadError: '插件加载错误', | ||||
| pluginLoadErrorDesc: '此插件将不会被安装', | pluginLoadErrorDesc: '此插件将不会被安装', | ||||
| installWarning: '此插件不允许安装。', | |||||
| }, | }, | ||||
| installFromGitHub: { | installFromGitHub: { | ||||
| installPlugin: '从 GitHub 安装插件', | installPlugin: '从 GitHub 安装插件', |
| LOST = 'lost', | LOST = 'lost', | ||||
| } | } | ||||
| export enum InstallationScope { | |||||
| ALL = 'all', | |||||
| NONE = 'none', | |||||
| OFFICIAL_ONLY = 'official_only', | |||||
| OFFICIAL_AND_PARTNER = 'official_and_specific_partners', | |||||
| } | |||||
| type License = { | type License = { | ||||
| status: LicenseStatus | status: LicenseStatus | ||||
| expired_at: string | null | expired_at: string | null | ||||
| } | } | ||||
| export type SystemFeatures = { | export type SystemFeatures = { | ||||
| plugin_installation_permission: { | |||||
| plugin_installation_scope: InstallationScope, | |||||
| restrict_to_marketplace_only: boolean | |||||
| }, | |||||
| sso_enforced_for_signin: boolean | sso_enforced_for_signin: boolean | ||||
| sso_enforced_for_signin_protocol: SSOProtocol | '' | sso_enforced_for_signin_protocol: SSOProtocol | '' | ||||
| sso_enforced_for_web: boolean | sso_enforced_for_web: boolean | ||||
| } | } | ||||
| export const defaultSystemFeatures: 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: false, | ||||
| sso_enforced_for_signin_protocol: '', | sso_enforced_for_signin_protocol: '', | ||||
| sso_enforced_for_web: false, | sso_enforced_for_web: false, |
| 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 { | import { | ||||
| CONTEXT_PLACEHOLDER_TEXT, | CONTEXT_PLACEHOLDER_TEXT, | ||||
| HISTORY_PLACEHOLDER_TEXT, | HISTORY_PLACEHOLDER_TEXT, | ||||
| // Set the value of basePath | // Set the value of basePath | ||||
| // example: /dify | // example: /dify | ||||
| export const basePath = '' | 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()}` | |||||
| } |