diff --git a/web/app/(commonLayout)/plugins/test/card/page.tsx b/web/app/(commonLayout)/plugins/test/card/page.tsx index efc569a733..85a00d5af0 100644 --- a/web/app/(commonLayout)/plugins/test/card/page.tsx +++ b/web/app/(commonLayout)/plugins/test/card/page.tsx @@ -1,6 +1,6 @@ 'use client' import Card from '@/app/components/plugins/card' -import { customTool, extensionDallE, modelGPT4, toolNeko, toolNotion } from '@/app/components/plugins/card/card-mock' +import { customTool, extensionDallE, modelGPT4, toolNotion } from '@/app/components/plugins/card/card-mock' // import PluginItem from '@/app/components/plugins/plugin-item' import CardMoreInfo from '@/app/components/plugins/card/card-more-info' // import ProviderCard from '@/app/components/plugins/provider-card' @@ -12,7 +12,23 @@ const PluginList = () => { return (
- { }} plugins={[toolNeko, { ...toolNeko, plugin_unique_identifier: `${toolNeko.plugin_unique_identifier}xxx` }]} /> + { }} fromDSLPayload={[ + { + type: 'marketplace', + value: { + plugin_unique_identifier: 'langgenius/google:0.0.2@dcb354c9d0fee60e6e9c9eb996e1e485bbef343ba8cd545c0cfb3ec80970f6f1', + }, + }, + { + type: 'github', + value: { + repo: 'YIXIAO0/test', + version: '1.11.5', + package: 'test.difypkg', + github_plugin_unique_identifier: 'yixiao0/test:0.0.1@3592166c87afcf944b4f13f27467a5c8f9e00bd349cb42033a072734a37431b4', + }, + }, + ]} />
{/*

Dify Plugin list

*/} {/*
diff --git a/web/app/components/plugins/install-plugin/install-bundle/index.tsx b/web/app/components/plugins/install-plugin/install-bundle/index.tsx index 6f20db7725..54f00d68a4 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/index.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/index.tsx @@ -3,7 +3,7 @@ import type { FC } from 'react' import Modal from '@/app/components/base/modal' import React, { useCallback, useState } from 'react' import { InstallStep } from '../../types' -import type { PluginDeclaration } from '../../types' +import type { Dependency } from '../../types' import Install from './steps/install' import { useTranslation } from 'react-i18next' @@ -17,13 +17,14 @@ export enum InstallType { type Props = { installType?: InstallType - plugins?: PluginDeclaration[] + fromDSLPayload: Dependency[] + // plugins?: PluginDeclaration[] onClose: () => void } const InstallBundle: FC = ({ installType = InstallType.fromMarketplace, - plugins = [], + fromDSLPayload, onClose, }) => { const { t } = useTranslation() @@ -54,7 +55,7 @@ const InstallBundle: FC = ({
{step === InstallStep.readyToInstall && ( )} diff --git a/web/app/components/plugins/install-plugin/install-bundle/item/github-item.tsx b/web/app/components/plugins/install-plugin/install-bundle/item/github-item.tsx new file mode 100644 index 0000000000..1a773ca6a8 --- /dev/null +++ b/web/app/components/plugins/install-plugin/install-bundle/item/github-item.tsx @@ -0,0 +1,47 @@ +'use client' +import type { FC } from 'react' +import React, { useEffect } from 'react' +import type { Dependency, Plugin } from '../../../types' +import { pluginManifestToCardPluginProps } from '../../utils' +import { useUploadGitHub } from '@/service/use-plugins' +import Loading from './loading' +import LoadedItem from './loaded-item' + +type Props = { + checked: boolean + onCheckedChange: (plugin: Plugin) => void + dependency: Dependency + onFetchedPayload: (payload: Plugin) => void +} + +const Item: FC = ({ + checked, + onCheckedChange, + dependency, + onFetchedPayload, +}) => { + const info = dependency.value + const { data } = useUploadGitHub({ + repo: info.repo!, + version: info.version!, + package: info.package!, + }) + const [payload, setPayload] = React.useState(null) + useEffect(() => { + if (data) { + const payload = pluginManifestToCardPluginProps(data.manifest) + onFetchedPayload(payload) + setPayload(payload) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [data]) + if (!payload) return + return ( + + ) +} +export default React.memo(Item) diff --git a/web/app/components/plugins/install-plugin/install-bundle/item/loaded-item.tsx b/web/app/components/plugins/install-plugin/install-bundle/item/loaded-item.tsx new file mode 100644 index 0000000000..94a9071dae --- /dev/null +++ b/web/app/components/plugins/install-plugin/install-bundle/item/loaded-item.tsx @@ -0,0 +1,36 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import type { Plugin } from '../../../types' +import Card from '../../../card' +import Checkbox from '@/app/components/base/checkbox' +import Badge, { BadgeState } from '@/app/components/base/badge/index' + +type Props = { + checked: boolean + onCheckedChange: (plugin: Plugin) => void + payload: Plugin +} + +const LoadedItem: FC = ({ + checked, + onCheckedChange, + payload, +}) => { + return ( +
+ onCheckedChange(payload)} + /> + {payload.version}} + /> +
+ ) +} + +export default React.memo(LoadedItem) diff --git a/web/app/components/plugins/install-plugin/install-bundle/item/loading.tsx b/web/app/components/plugins/install-plugin/install-bundle/item/loading.tsx new file mode 100644 index 0000000000..e89022aad6 --- /dev/null +++ b/web/app/components/plugins/install-plugin/install-bundle/item/loading.tsx @@ -0,0 +1,12 @@ +'use client' +import React from 'react' + +const Loading = () => { + return ( +
+ Loading... +
+ ) +} + +export default React.memo(Loading) diff --git a/web/app/components/plugins/install-plugin/install-bundle/item/marketplace-item.tsx b/web/app/components/plugins/install-plugin/install-bundle/item/marketplace-item.tsx new file mode 100644 index 0000000000..5f2e9433a1 --- /dev/null +++ b/web/app/components/plugins/install-plugin/install-bundle/item/marketplace-item.tsx @@ -0,0 +1,29 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import type { Plugin } from '../../../types' +import Loading from './loading' +import LoadedItem from './loaded-item' + +type Props = { + checked: boolean + onCheckedChange: (plugin: Plugin) => void + payload?: Plugin +} + +const MarketPlaceItem: FC = ({ + checked, + onCheckedChange, + payload, +}) => { + if (!payload) return + return ( + + ) +} + +export default React.memo(MarketPlaceItem) diff --git a/web/app/components/plugins/install-plugin/install-bundle/steps/install-by-dsl-list.tsx b/web/app/components/plugins/install-plugin/install-bundle/steps/install-by-dsl-list.tsx new file mode 100644 index 0000000000..eae9394dc1 --- /dev/null +++ b/web/app/components/plugins/install-plugin/install-bundle/steps/install-by-dsl-list.tsx @@ -0,0 +1,83 @@ +'use client' +import type { FC } from 'react' +import React, { useCallback, useEffect, useMemo } from 'react' +import type { Dependency, Plugin } from '../../../types' +import MarketplaceItem from '../item/marketplace-item' +import GithubItem from '../item/github-item' +import { useFetchPluginsInMarketPlaceByIds } from '@/service/use-plugins' +import produce from 'immer' +import { useGetState } from 'ahooks' + +type Props = { + fromDSLPayload: Dependency[] + selectedPlugins: Plugin[] + handleSelect: (plugin: Plugin) => void + onLoadedAllPlugin: () => void +} + +const InstallByDSLList: FC = ({ + fromDSLPayload, + selectedPlugins, + handleSelect, + onLoadedAllPlugin, +}) => { + const { isLoading: isFetchingMarketplaceData, data: marketplaceRes } = useFetchPluginsInMarketPlaceByIds(fromDSLPayload.filter(d => d.type === 'marketplace').map(d => d.value.plugin_unique_identifier!)) + const [plugins, setPlugins, getPlugins] = useGetState([]) + const handlePlugInFetched = useCallback((index: number) => { + return (p: Plugin) => { + setPlugins(plugins.map((item, i) => i === index ? p : item)) + } + }, [plugins]) + + const marketPlaceInDSLIndex = useMemo(() => { + const res: number[] = [] + fromDSLPayload.forEach((d, index) => { + if (d.type === 'marketplace') + res.push(index) + }) + return res + }, [fromDSLPayload]) + + useEffect(() => { + if (!isFetchingMarketplaceData && marketplaceRes?.data.plugins && marketplaceRes?.data.plugins.length > 0) { + const payloads = marketplaceRes?.data.plugins + + const nextPlugins = produce(getPlugins(), (draft) => { + marketPlaceInDSLIndex.forEach((index, i) => { + draft[index] = payloads[i] + }) + }) + setPlugins(nextPlugins) + // marketplaceRes?.data.plugins + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isFetchingMarketplaceData]) + + const isLoadedAllData = fromDSLPayload.length === plugins.length && plugins.every(p => !!p) + useEffect(() => { + if (isLoadedAllData) + onLoadedAllPlugin() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isLoadedAllData]) + return ( + <> + {fromDSLPayload.map((d, index) => ( + d.type === 'github' + ? p.plugin_id === d.value.plugin_unique_identifier)} + onCheckedChange={handleSelect} + dependency={d} + onFetchedPayload={handlePlugInFetched(index)} + /> + : p.plugin_id === d.value.plugin_unique_identifier)} + onCheckedChange={handleSelect} + payload={plugins[index] as Plugin} + /> + ))} + + ) +} +export default React.memo(InstallByDSLList) diff --git a/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx b/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx index 9b0e996f82..1423e399b8 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx @@ -1,41 +1,42 @@ 'use client' import type { FC } from 'react' -import React from 'react' -import type { PluginDeclaration } from '../../../types' -import Card from '../../../card' +import React, { useCallback } from 'react' +import type { Dependency, Plugin } from '../../../types' import Button from '@/app/components/base/button' import { RiLoader2Line } from '@remixicon/react' -import Badge, { BadgeState } from '@/app/components/base/badge/index' -import { pluginManifestToCardPluginProps } from '../../utils' import { useTranslation } from 'react-i18next' -import Checkbox from '@/app/components/base/checkbox' +import InstallByDSLList from './install-by-dsl-list' const i18nPrefix = 'plugin.installModal' type Props = { - plugins: PluginDeclaration[], + fromDSLPayload: Dependency[] onCancel: () => void } const Install: FC = ({ - plugins, + fromDSLPayload, onCancel, }) => { const { t } = useTranslation() - const [selectedPlugins, setSelectedPlugins] = React.useState([]) + const [selectedPlugins, setSelectedPlugins] = React.useState([]) const selectedPluginsNum = selectedPlugins.length - const handleSelect = (plugin: PluginDeclaration) => { + + const handleSelect = (plugin: Plugin) => { return () => { - const isSelected = !!selectedPlugins.find(p => p.plugin_unique_identifier === plugin.plugin_unique_identifier) + const isSelected = !!selectedPlugins.find(p => p.plugin_id === plugin.plugin_id) let nextSelectedPlugins if (isSelected) - nextSelectedPlugins = selectedPlugins.filter(p => p.plugin_unique_identifier !== plugin.plugin_unique_identifier) + nextSelectedPlugins = selectedPlugins.filter(p => p.plugin_id !== plugin.plugin_id) else nextSelectedPlugins = [...selectedPlugins, plugin] setSelectedPlugins(nextSelectedPlugins) } } - const [isInstalling, setIsInstalling] = React.useState(false) + const [canInstall, setCanInstall] = React.useState(false) + const handleLoadedAllPlugin = useCallback(() => { + setCanInstall(true) + }, []) const handleInstall = () => { } @@ -46,25 +47,17 @@ const Install: FC = ({

{t(`${i18nPrefix}.${selectedPluginsNum > 1 ? 'readyToInstallPackages' : 'readyToInstallPackage'}`, { num: selectedPluginsNum })}

- {plugins.map(plugin => ( -
- p.plugin_unique_identifier === plugin.plugin_unique_identifier)} - onCheck={handleSelect(plugin)} - /> - {plugin.version}} - /> -
- ))} +
{/* Action Buttons */}
- {!isInstalling && ( + {!canInstall && ( @@ -72,11 +65,11 @@ const Install: FC = ({
diff --git a/web/service/use-plugins.ts b/web/service/use-plugins.ts index c9f8058e8f..196f9bd5ff 100644 --- a/web/service/use-plugins.ts +++ b/web/service/use-plugins.ts @@ -7,6 +7,7 @@ import type { Permissions, PluginTask, PluginsFromMarketplaceResponse, + uploadGitHubResponse, } from '@/app/components/plugins/types' import type { PluginsSearchParams, @@ -60,6 +61,19 @@ export const useInstallPackageFromLocal = () => { }) } +export const useUploadGitHub = (payload: { + repo: string + version: string + package: string +}) => { + return useQuery({ + queryKey: [NAME_SPACE, 'uploadGitHub', payload], + queryFn: () => post('/workspaces/current/plugin/upload/github', { + body: payload, + }), + }) +} + export const useDebugKey = () => { return useQuery({ queryKey: [NAME_SPACE, 'debugKey'], @@ -123,6 +137,17 @@ export const useMutationPluginsFromMarketplace = () => { }) } +export const useFetchPluginsInMarketPlaceByIds = (unique_identifiers: string[]) => { + return useQuery({ + queryKey: [NAME_SPACE, 'fetchPluginsInMarketPlaceByIds', unique_identifiers], + queryFn: () => postMarketplace<{ data: PluginsFromMarketplaceResponse }>('/plugins/identifier/batch', { + body: { + unique_identifiers, + }, + }), + }) +} + const usePluginTaskListKey = [NAME_SPACE, 'pluginTaskList'] export const usePluginTaskList = () => { const [enabled, setEnabled] = useState(true)