diff --git a/web/app/components/plugins/card/index.tsx b/web/app/components/plugins/card/index.tsx index 049d92536b..1ba801eac3 100644 --- a/web/app/components/plugins/card/index.tsx +++ b/web/app/components/plugins/card/index.tsx @@ -11,7 +11,7 @@ import Placeholder from './base/placeholder' import cn from '@/utils/classnames' import { useGetLanguage } from '@/context/i18n' -type Props = { +export type Props = { className?: string payload: Plugin titleLeft?: React.ReactNode diff --git a/web/app/components/plugins/install-plugin/install-from-local-package/index.tsx b/web/app/components/plugins/install-plugin/install-from-local-package/index.tsx index 6fd0feead9..f3e618ad08 100644 --- a/web/app/components/plugins/install-plugin/install-from-local-package/index.tsx +++ b/web/app/components/plugins/install-plugin/install-from-local-package/index.tsx @@ -1,57 +1,47 @@ 'use client' -import React, { useCallback, useEffect, useState } from 'react' +import React, { useCallback, useState } from 'react' import { useContext } from 'use-context-selector' -import { RiLoader2Line } from '@remixicon/react' -import Card from '../../card' -import { toolNotion } from '../../card/card-mock' import Modal from '@/app/components/base/modal' -import Button from '@/app/components/base/button' import I18n from '@/context/i18n' +import type { PluginDeclaration } from '../../types' +import { InstallStep } from '../../types' +import Uploading from './steps/uploading' +import Install from './steps/install' +import Installed from './steps/installed' type InstallFromLocalPackageProps = { file: File + onSuccess: () => void onClose: () => void } -const InstallFromLocalPackage: React.FC = ({ onClose }) => { - const [status, setStatus] = useState<'uploading' | 'ready' | 'installing' | 'installed'>('uploading') +const InstallFromLocalPackage: React.FC = ({ + file, + onClose +}) => { + const [step, setStep] = useState(InstallStep.uploading) const { locale } = useContext(I18n) - useEffect(() => { - const timer = setTimeout(() => setStatus('ready'), 1500) - return () => clearTimeout(timer) + const [uniqueIdentifier, setUniqueIdentifier] = useState(null) + const [manifest, setManifest] = useState({ + name: 'Notion Sync', + description: 'Sync your Notion notes with Dify', + } as any) + + const handleUploaded = useCallback((result: { + uniqueIdentifier: string + manifest: PluginDeclaration + }) => { + setUniqueIdentifier(result.uniqueIdentifier) + setManifest(result.manifest) + setStep(InstallStep.readyToInstall) }, []) - const handleInstall = useCallback(async () => { - setStatus('installing') - await new Promise(resolve => setTimeout(resolve, 1000)) - setStatus('installed') + const handleInstalled = useCallback(async () => { + setStep(InstallStep.installed) }, []) - const renderStatusMessage = () => { - switch (status) { - case 'uploading': - return ( -
- -
- Uploading notion-sync.difypkg ... -
-
- ) - case 'installed': - return

The plugin has been installed successfully.

- default: - return ( -
-

About to install the following plugin.

-

Please make sure that you only install plugins from a trusted source.

-
- ) - } - } - return ( = ({ onClo Install plugin -
- {renderStatusMessage()} -
- + )} + { + step === InstallStep.readyToInstall && ( + -
-
-
- {status === 'installed' - ? ( - - ) - : ( - <> - - - - )} -
+ ) + } + { + step === InstallStep.installed && ( + + ) + }
) } diff --git a/web/app/components/plugins/install-plugin/install-from-local-package/steps/install.tsx b/web/app/components/plugins/install-plugin/install-from-local-package/steps/install.tsx new file mode 100644 index 0000000000..6ccfd8a88a --- /dev/null +++ b/web/app/components/plugins/install-plugin/install-from-local-package/steps/install.tsx @@ -0,0 +1,63 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import type { PluginDeclaration } from '../../../types' +import Card from '../../../card' +import { pluginManifestToCardPluginProps } from '../../utils' +import Button from '@/app/components/base/button' +import { sleep } from '@/utils' + +type Props = { + payload: PluginDeclaration + onCancel: () => void + onInstalled: () => void +} + +const Installed: FC = ({ + payload, + onCancel, + onInstalled, +}) => { + const [isInstalling, setIsInstalling] = React.useState(false) + + const handleInstall = async () => { + if (isInstalling) return + setIsInstalling(true) + await sleep(1500) + onInstalled() + } + + return ( + <> +
+
+

About to install the following plugin.

+

Please make sure that you only install plugins from a trusted source.

+
+
+ +
+
+ {/* Action Buttons */} +
+ {!isInstalling && ( + + )} + +
+ + ) +} +export default React.memo(Installed) diff --git a/web/app/components/plugins/install-plugin/install-from-local-package/steps/installed.tsx b/web/app/components/plugins/install-plugin/install-from-local-package/steps/installed.tsx new file mode 100644 index 0000000000..fa07359ecc --- /dev/null +++ b/web/app/components/plugins/install-plugin/install-from-local-package/steps/installed.tsx @@ -0,0 +1,44 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import type { PluginDeclaration } from '../../../types' +import Card from '../../../card' +import Button from '@/app/components/base/button' +import { pluginManifestToCardPluginProps } from '../../utils' + +type Props = { + payload: PluginDeclaration + onCancel: () => void + +} + +const Installed: FC = ({ + payload, + onCancel +}) => { + return ( + <> +
+

The plugin has been installed successfully.

+
+ +
+
+ {/* Action Buttons */} +
+ +
+ + ) +} +export default React.memo(Installed) diff --git a/web/app/components/plugins/install-plugin/install-from-local-package/steps/uploading.tsx b/web/app/components/plugins/install-plugin/install-from-local-package/steps/uploading.tsx new file mode 100644 index 0000000000..5f2ce5ce74 --- /dev/null +++ b/web/app/components/plugins/install-plugin/install-from-local-package/steps/uploading.tsx @@ -0,0 +1,76 @@ +'use client' +import type { FC } from 'react' +import React from 'react' +import { RiLoader2Line } from '@remixicon/react' +import Card from '../../../card' +import type { PluginDeclaration } from '../../../types' +import Button from '@/app/components/base/button' +import { sleep } from '@/utils' + +type Props = { + file: File + onCancel: () => void + onUploaded: (result: { + uniqueIdentifier: string + manifest: PluginDeclaration + }) => void +} + +const Uploading: FC = ({ + file, + onCancel, + onUploaded, +}) => { + const fileName = file.name + const handleUpload = async () => { + await sleep(1500) + onUploaded({ + uniqueIdentifier: 'yeuoly/neko:0.0.1@5395654da2c0b919b3d9b946a1a0545b737004380765e5f3b8c49976d3276c87', + manifest: { + name: 'Notion Sync', + description: 'Sync your Notion notes with Dify', + } as any, + }) + } + + React.useEffect(() => { + handleUpload() + }, []) + return ( + <> +
+
+ +
+ Uploading {fileName}... +
+
+
+ +
+
+ + {/* Action Buttons */} +
+ + +
+ + ) +} + +export default React.memo(Uploading) diff --git a/web/app/components/plugins/install-plugin/utils.ts b/web/app/components/plugins/install-plugin/utils.ts new file mode 100644 index 0000000000..f3d9158d53 --- /dev/null +++ b/web/app/components/plugins/install-plugin/utils.ts @@ -0,0 +1,21 @@ +import type { Plugin, PluginDeclaration } from "../types" + +export const pluginManifestToCardPluginProps = (pluginManifest: PluginDeclaration): Plugin => { + return { + type: pluginManifest.category, + category: pluginManifest.category, + name: pluginManifest.name, + version: pluginManifest.version, + latest_version: '', + org: pluginManifest.author, + label: pluginManifest.label, + brief: pluginManifest.description, + icon: pluginManifest.icon, + introduction: '', + repository: '', + install_count: 0, + endpoint: { + settings: [] + } + } +} diff --git a/web/app/components/plugins/plugin-page/index.tsx b/web/app/components/plugins/plugin-page/index.tsx index 66c2cd1318..598c2d7015 100644 --- a/web/app/components/plugins/plugin-page/index.tsx +++ b/web/app/components/plugins/plugin-page/index.tsx @@ -149,7 +149,11 @@ const PluginPage = ({ Drop plugin package here to install {currentFile && ( - { })} /> + { })} + onSuccess={() => { }} + /> )} { {selectedAction === 'marketplace' && setSelectedAction(null)} />} - {selectedAction === 'github' && setSelectedAction(null)}/>} + {selectedAction === 'github' && setSelectedAction(null)} />} {selectedAction === 'local' && selectedFile && ( setSelectedAction(null)}/> + onClose={() => setSelectedAction(null)} + onSuccess={() => { }} + /> ) } diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts index b6f00802f3..adf8c5a0f5 100644 --- a/web/app/components/plugins/types.ts +++ b/web/app/components/plugins/types.ts @@ -52,6 +52,7 @@ export type EndpointListItem = { hook_id: string } +// Plugin manifest export type PluginDeclaration = { version: string author: string @@ -150,6 +151,13 @@ export type UpdateEndpointRequest = { name: string } +export enum InstallStep { + uploading = 'uploading', + readyToInstall = 'readyToInstall', + installing = 'installing', + installed = 'installed', +} + export type GitHubAsset = { id: number name: string