From c5c06c18f1a9a32476277fd6e7909a9ff6429b14 Mon Sep 17 00:00:00 2001 From: Joel Date: Tue, 19 Nov 2024 15:05:15 +0800 Subject: [PATCH] feat: can upload and parse bundle --- .../(commonLayout)/plugins/test/card/page.tsx | 2 +- .../install-plugin/install-bundle/index.tsx | 34 +++------ .../install-bundle/ready-to-install.tsx | 48 +++++++++++++ .../install-bundle/steps/installed.tsx | 4 +- .../install-from-local-package/index.tsx | 71 +++++++++---------- .../ready-to-install.tsx | 68 ++++++++++++++++++ .../steps/uploading.tsx | 21 ++++-- .../plugins/plugin-page/empty/index.tsx | 7 +- .../components/plugins/plugin-page/index.tsx | 3 +- .../plugin-page/install-plugin-dropdown.tsx | 5 +- web/config/index.ts | 2 + web/service/plugins.ts | 6 +- 12 files changed, 188 insertions(+), 83 deletions(-) create mode 100644 web/app/components/plugins/install-plugin/install-bundle/ready-to-install.tsx create mode 100644 web/app/components/plugins/install-plugin/install-from-local-package/ready-to-install.tsx diff --git a/web/app/(commonLayout)/plugins/test/card/page.tsx b/web/app/(commonLayout)/plugins/test/card/page.tsx index 534789c00c..02e80d8563 100644 --- a/web/app/(commonLayout)/plugins/test/card/page.tsx +++ b/web/app/(commonLayout)/plugins/test/card/page.tsx @@ -47,7 +47,7 @@ const PluginList = () => { { type: 'marketplace', value: { - plugin_unique_identifier: 'langgenius/openai:0.0.1@f88fdb98d104466db16a425bfe3af8c1bcad45047a40fb802d98a989ac57a5a3', + plugin_unique_identifier: 'langgenius/openai:0.0.2@7baee9635a07573ea192621ebfdacb39db466fa691e75255beaf48bf41d44375', }, }, ]} /> 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 6a7397fa6c..89323de3a0 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/index.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/index.tsx @@ -3,9 +3,8 @@ import type { FC } from 'react' import Modal from '@/app/components/base/modal' import React, { useCallback, useState } from 'react' import { InstallStep } from '../../types' -import type { Dependency, InstallStatusResponse, Plugin } from '../../types' -import Install from './steps/install' -import Installed from './steps/installed' +import type { Dependency } from '../../types' +import ReadyToInstall from './ready-to-install' import { useTranslation } from 'react-i18next' const i18nPrefix = 'plugin.installModal' @@ -30,8 +29,7 @@ const InstallBundle: FC = ({ }) => { const { t } = useTranslation() const [step, setStep] = useState(installType === InstallType.fromMarketplace ? InstallStep.readyToInstall : InstallStep.uploading) - const [installedPlugins, setInstalledPlugins] = useState([]) - const [installStatus, setInstallStatus] = useState([]) + const getTitle = useCallback(() => { if (step === InstallStep.uploadFailed) return t(`${i18nPrefix}.uploadFailed`) @@ -43,12 +41,6 @@ const InstallBundle: FC = ({ return t(`${i18nPrefix}.installPlugin`) }, [step, t]) - const handleInstalled = useCallback((plugins: Plugin[], installStatus: InstallStatusResponse[]) => { - setInstallStatus(installStatus) - setInstalledPlugins(plugins) - setStep(InstallStep.installed) - }, []) - return ( = ({ {getTitle()} - {step === InstallStep.readyToInstall && ( - - )} - {step === InstallStep.installed && ( - - )} + ) } diff --git a/web/app/components/plugins/install-plugin/install-bundle/ready-to-install.tsx b/web/app/components/plugins/install-plugin/install-bundle/ready-to-install.tsx new file mode 100644 index 0000000000..e9f89cb5c6 --- /dev/null +++ b/web/app/components/plugins/install-plugin/install-bundle/ready-to-install.tsx @@ -0,0 +1,48 @@ +'use client' +import type { FC } from 'react' +import React, { useCallback, useState } from 'react' +import { InstallStep } from '../../types' +import Install from './steps/install' +import Installed from './steps/installed' +import type { Dependency, InstallStatusResponse, Plugin } from '../../types' + +type Props = { + step: InstallStep + onStepChange: (step: InstallStep) => void, + dependencies: Dependency[] + onClose: () => void +} + +const ReadyToInstall: FC = ({ + step, + onStepChange, + dependencies, + onClose, +}) => { + const [installedPlugins, setInstalledPlugins] = useState([]) + const [installStatus, setInstallStatus] = useState([]) + const handleInstalled = useCallback((plugins: Plugin[], installStatus: InstallStatusResponse[]) => { + setInstallStatus(installStatus) + setInstalledPlugins(plugins) + onStepChange(InstallStep.installed) + }, [onStepChange]) + return ( + <> + {step === InstallStep.readyToInstall && ( + + )} + {step === InstallStep.installed && ( + + )} + + ) +} +export default React.memo(ReadyToInstall) diff --git a/web/app/components/plugins/install-plugin/install-bundle/steps/installed.tsx b/web/app/components/plugins/install-plugin/install-bundle/steps/installed.tsx index 736930d88c..6b339bdd8c 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/steps/installed.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/steps/installed.tsx @@ -1,7 +1,7 @@ 'use client' import type { FC } from 'react' import React from 'react' -import type { Plugin } from '../../../types' +import type { InstallStatusResponse, Plugin } from '../../../types' import Card from '@/app/components/plugins/card' import Button from '@/app/components/base/button' import { useTranslation } from 'react-i18next' @@ -11,7 +11,7 @@ import { MARKETPLACE_API_PREFIX } from '@/config' type Props = { list: Plugin[] - installStatus: { success: boolean, isFromMarketPlace: boolean }[] + installStatus: InstallStatusResponse[] onCancel: () => void } 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 86f31c36f2..7cfaafb668 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 @@ -2,14 +2,13 @@ import React, { useCallback, useState } from 'react' import Modal from '@/app/components/base/modal' -import type { PluginDeclaration } from '../../types' +import type { Dependency, PluginDeclaration } from '../../types' import { InstallStep } from '../../types' import Uploading from './steps/uploading' -import Install from './steps/install' -import Installed from '../base/installed' import { useTranslation } from 'react-i18next' import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon' -import { useInvalidateInstalledPluginList } from '@/service/use-plugins' +import ReadyToInstallPackage from './ready-to-install' +import ReadyToInstallBundle from '../install-bundle/ready-to-install' const i18nPrefix = 'plugin.installModal' @@ -29,7 +28,8 @@ const InstallFromLocalPackage: React.FC = ({ const [uniqueIdentifier, setUniqueIdentifier] = useState(null) const [manifest, setManifest] = useState(null) const [errorMsg, setErrorMsg] = useState(null) - const invalidateInstalledPluginList = useInvalidateInstalledPluginList() + const isBundle = file.name.endsWith('.bundle') + const [dependencies, setDependencies] = useState([]) const getTitle = useCallback(() => { if (step === InstallStep.uploadFailed) @@ -44,7 +44,7 @@ const InstallFromLocalPackage: React.FC = ({ const { getIconUrl } = useGetIcon() - const handleUploaded = useCallback(async (result: { + const handlePackageUploaded = useCallback(async (result: { uniqueIdentifier: string manifest: PluginDeclaration }) => { @@ -61,22 +61,16 @@ const InstallFromLocalPackage: React.FC = ({ setStep(InstallStep.readyToInstall) }, [getIconUrl]) + const handleBundleUploaded = useCallback((result: Dependency[]) => { + setDependencies(result) + setStep(InstallStep.readyToInstall) + }, []) + const handleUploadFail = useCallback((errorMsg: string) => { setErrorMsg(errorMsg) setStep(InstallStep.uploadFailed) }, []) - const handleInstalled = useCallback(() => { - invalidateInstalledPluginList() - setStep(InstallStep.installed) - }, [invalidateInstalledPluginList]) - - const handleFailed = useCallback((errorMsg?: string) => { - setStep(InstallStep.installFailed) - if (errorMsg) - setErrorMsg(errorMsg) - }, []) - return ( = ({ {step === InstallStep.uploading && ( )} - { - step === InstallStep.readyToInstall && ( - - ) - } - { - ([InstallStep.uploadFailed, InstallStep.installed, InstallStep.installFailed].includes(step)) && ( - - ) - } + {isBundle ? ( + + ) : ( + + )} ) } diff --git a/web/app/components/plugins/install-plugin/install-from-local-package/ready-to-install.tsx b/web/app/components/plugins/install-plugin/install-from-local-package/ready-to-install.tsx new file mode 100644 index 0000000000..9f81b1e918 --- /dev/null +++ b/web/app/components/plugins/install-plugin/install-from-local-package/ready-to-install.tsx @@ -0,0 +1,68 @@ +'use client' +import type { FC } from 'react' +import React, { useCallback } from 'react' +import type { PluginDeclaration } from '../../types' +import { InstallStep } from '../../types' +import Install from './steps/install' +import Installed from '../base/installed' +import { useInvalidateInstalledPluginList } from '@/service/use-plugins' + +type Props = { + step: InstallStep + onStepChange: (step: InstallStep) => void, + onClose: () => void + uniqueIdentifier: string | null, + manifest: PluginDeclaration | null, + errorMsg: string | null, + onError: (errorMsg: string) => void, +} + +const ReadyToInstall: FC = ({ + step, + onStepChange, + onClose, + uniqueIdentifier, + manifest, + errorMsg, + onError, +}) => { + const invalidateInstalledPluginList = useInvalidateInstalledPluginList() + + const handleInstalled = useCallback(() => { + invalidateInstalledPluginList() + onStepChange(InstallStep.installed) + }, [invalidateInstalledPluginList, onStepChange]) + + const handleFailed = useCallback((errorMsg?: string) => { + onStepChange(InstallStep.installFailed) + if (errorMsg) + onError(errorMsg) + }, [onError, onStepChange]) + + return ( + <> + { + step === InstallStep.readyToInstall && ( + + ) + } + { + ([InstallStep.uploadFailed, InstallStep.installed, InstallStep.installFailed].includes(step)) && ( + + ) + } + + ) +} +export default React.memo(ReadyToInstall) 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 index 499326c63e..61e762ce60 100644 --- 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 @@ -3,34 +3,37 @@ import type { FC } from 'react' import React from 'react' import { RiLoader2Line } from '@remixicon/react' import Card from '../../../card' -import type { PluginDeclaration } from '../../../types' +import type { Dependency, PluginDeclaration } from '../../../types' import Button from '@/app/components/base/button' import { useTranslation } from 'react-i18next' -import { uploadPackageFile } from '@/service/plugins' +import { uploadFile } from '@/service/plugins' const i18nPrefix = 'plugin.installModal' type Props = { + isBundle: boolean file: File onCancel: () => void - onUploaded: (result: { + onPackageUploaded: (result: { uniqueIdentifier: string manifest: PluginDeclaration }) => void + onBundleUploaded: (result: Dependency[]) => void onFailed: (errorMsg: string) => void } const Uploading: FC = ({ + isBundle, file, onCancel, - onUploaded, + onPackageUploaded, + onBundleUploaded, onFailed, }) => { const { t } = useTranslation() const fileName = file.name const handleUpload = async () => { try { - const res = await uploadPackageFile(file) - onUploaded(res) + await uploadFile(file, isBundle) } catch (e: any) { if (e.response?.message) { @@ -38,7 +41,11 @@ const Uploading: FC = ({ } else { // Why it would into this branch? const res = e.response - onUploaded({ + if (isBundle) { + onBundleUploaded(res) + return + } + onPackageUploaded({ uniqueIdentifier: res.unique_identifier, manifest: res.manifest, }) diff --git a/web/app/components/plugins/plugin-page/empty/index.tsx b/web/app/components/plugins/plugin-page/empty/index.tsx index 06dc41492f..408f724df7 100644 --- a/web/app/components/plugins/plugin-page/empty/index.tsx +++ b/web/app/components/plugins/plugin-page/empty/index.tsx @@ -10,6 +10,7 @@ import { useSelector as useAppContextSelector } from '@/context/app-context' import Line from '../../marketplace/empty/line' import { useInstalledPluginList, useInvalidateInstalledPluginList } from '@/service/use-plugins' import { useTranslation } from 'react-i18next' +import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config' const Empty = () => { const { t } = useTranslation() @@ -42,11 +43,11 @@ const Empty = () => { {/* skeleton */}
{Array.from({ length: 20 }).fill(0).map((_, i) => ( -
+
))}
{/* mask */} -
+
{[ diff --git a/web/app/components/plugins/plugin-page/index.tsx b/web/app/components/plugins/plugin-page/index.tsx index d089d37543..e743e248bc 100644 --- a/web/app/components/plugins/plugin-page/index.tsx +++ b/web/app/components/plugins/plugin-page/index.tsx @@ -32,6 +32,7 @@ import type { PluginDeclaration, PluginManifestInMarket } from '../types' import { sleep } from '@/utils' import { fetchManifestFromMarketPlace } from '@/service/plugins' import { marketplaceApiPrefix } from '@/config' +import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config' const PACKAGE_IDS_KEY = 'package-ids' @@ -186,7 +187,7 @@ const PluginPage = ({ className="hidden" type="file" id="fileUploader" - accept='.difypkg' + accept={SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS} onChange={fileChangeHandle ?? (() => { })} /> diff --git a/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx b/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx index 826aba334b..e8d68e1bbf 100644 --- a/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx +++ b/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx @@ -17,6 +17,7 @@ import { import { useSelector as useAppContextSelector } from '@/context/app-context' import { useInvalidateInstalledPluginList } from '@/service/use-plugins' import { useTranslation } from 'react-i18next' +import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config' type Props = { onSwitchToMarketplaceTab: () => void @@ -81,7 +82,7 @@ const InstallPluginDropdown = ({ ref={fileInputRef} style={{ display: 'none' }} onChange={handleFileChange} - accept='.difypkg' + accept={SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS} />
{[ @@ -126,7 +127,7 @@ const InstallPluginDropdown = ({ && ( setSelectedAction(null)} - onSuccess={() => {}} + onSuccess={() => { }} /> ) } diff --git a/web/config/index.ts b/web/config/index.ts index 1de973f9a2..6e8c4a630c 100644 --- a/web/config/index.ts +++ b/web/config/index.ts @@ -273,3 +273,5 @@ export const TEXT_GENERATION_TIMEOUT_MS = textGenerationTimeoutMs export const DISABLE_UPLOAD_IMAGE_AS_ICON = process.env.NEXT_PUBLIC_DISABLE_UPLOAD_IMAGE_AS_ICON === 'true' export const GITHUB_ACCESS_TOKEN = process.env.NEXT_PUBLIC_GITHUB_ACCESS_TOKEN || globalThis.document?.body?.getAttribute('data-public-github-access-token') || '' + +export const SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS = '.difypkg,.bundle' diff --git a/web/service/plugins.ts b/web/service/plugins.ts index 46a90b885c..28bf6c44f4 100644 --- a/web/service/plugins.ts +++ b/web/service/plugins.ts @@ -16,13 +16,13 @@ import type { MarketplaceCollectionsResponse, } from '@/app/components/plugins/marketplace/types' -export const uploadPackageFile = async (file: File) => { +export const uploadFile = async (file: File, isBundle: boolean) => { const formData = new FormData() - formData.append('pkg', file) + formData.append(isBundle ? 'bundle' : 'pkg', file) return upload({ xhr: new XMLHttpRequest(), data: formData, - }, false, '/workspaces/current/plugin/upload/pkg') + }, false, `/workspaces/current/plugin/upload/${isBundle ? 'bundle' : 'pkg'}`) } export const updateFromMarketPlace = async (body: Record) => {