diff --git a/web/app/(commonLayout)/plugins/page.tsx b/web/app/(commonLayout)/plugins/page.tsx index 516cc138a2..f44ff6522a 100644 --- a/web/app/(commonLayout)/plugins/page.tsx +++ b/web/app/(commonLayout)/plugins/page.tsx @@ -8,7 +8,7 @@ const PluginList = async () => { return ( } - marketplace={} + marketplace={} /> ) } diff --git a/web/app/components/app/configuration/index.tsx b/web/app/components/app/configuration/index.tsx index 312f00ff1f..70ce4274d7 100644 --- a/web/app/components/app/configuration/index.tsx +++ b/web/app/components/app/configuration/index.tsx @@ -72,7 +72,6 @@ import { SupportUploadFileTypes } from '@/app/components/workflow/types' import NewFeaturePanel from '@/app/components/base/features/new-feature-panel' import { fetchFileUploadConfig } from '@/service/common' import { correctProvider } from '@/utils' -import PluginDependency from '@/app/components/workflow/plugin-dependency' type PublishConfig = { modelConfig: ModelConfig @@ -1042,7 +1041,6 @@ const Configuration: FC = () => { onAutoAddPromptVariable={handleAddPromptVariable} /> )} - diff --git a/web/app/components/app/create-from-dsl-modal/index.tsx b/web/app/components/app/create-from-dsl-modal/index.tsx index e697359e58..12e2266c22 100644 --- a/web/app/components/app/create-from-dsl-modal/index.tsx +++ b/web/app/components/app/create-from-dsl-modal/index.tsx @@ -25,7 +25,8 @@ import AppsFull from '@/app/components/billing/apps-full-in-dialog' import { NEED_REFRESH_APP_LIST_KEY } from '@/config' import { getRedirection } from '@/utils/app-redirection' import cn from '@/utils/classnames' -import { useMutationCheckDependenciesBeforeImportDSL } from '@/service/use-plugins' +import { useStore as usePluginDependencyStore } from '@/app/components/workflow/plugin-dependency/store' +import PluginDependency from '@/app/components/workflow/plugin-dependency' type CreateFromDSLModalProps = { show: boolean @@ -48,7 +49,6 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS const [fileContent, setFileContent] = useState() const [currentTab, setCurrentTab] = useState(activeTab) const [dslUrlValue, setDslUrlValue] = useState(dslUrl) - const { mutateAsync } = useMutationCheckDependenciesBeforeImportDSL() const [showErrorModal, setShowErrorModal] = useState(false) const [versions, setVersions] = useState<{ importedVersion: string; systemVersion: string }>() const [importId, setImportId] = useState() @@ -92,20 +92,22 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS mode: DSLImportMode.YAML_CONTENT, yaml_content: fileContent || '', }) - await mutateAsync({ dslString: fileContent }) } if (currentTab === CreateFromDSLModalTab.FROM_URL) { response = await importDSL({ mode: DSLImportMode.YAML_URL, yaml_url: dslUrlValue || '', }) - await mutateAsync({ url: dslUrlValue }) } if (!response) return - const { id, status, app_id, imported_dsl_version, current_dsl_version } = response + const { id, status, app_id, imported_dsl_version, current_dsl_version, leaked_dependencies } = response + if (leaked_dependencies?.length) { + const { setDependencies } = usePluginDependencyStore.getState() + setDependencies(leaked_dependencies) + } if (status === DSLImportStatus.COMPLETED || status === DSLImportStatus.COMPLETED_WITH_WARNINGS) { if (onSuccess) onSuccess() @@ -272,7 +274,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS >
{t('app.newApp.appCreateDSLErrorTitle')}
-
+
{t('app.newApp.appCreateDSLErrorPart1')}
{t('app.newApp.appCreateDSLErrorPart2')}

@@ -280,6 +282,7 @@ const CreateFromDSLModal = ({ show, onSuccess, onClose, activeTab = CreateFromDS
{t('app.newApp.appCreateDSLErrorPart4')}{versions?.systemVersion}
+
diff --git a/web/app/components/base/chat/__tests__/utils.spec.ts b/web/app/components/base/chat/__tests__/utils.spec.ts index 0bff8a77a1..990e59d5ee 100644 --- a/web/app/components/base/chat/__tests__/utils.spec.ts +++ b/web/app/components/base/chat/__tests__/utils.spec.ts @@ -263,7 +263,7 @@ describe('build chat item tree and get thread messages', () => { expect(tree7).toMatchSnapshot() }) - const partialMessages2 = (partialMessages as ChatItemInTree[]) + const partialMessages2 = partialMessages as ChatItemInTree[] const tree8 = buildChatItemTree(partialMessages2) it('should work with partial messages 2', () => { expect(tree8).toMatchSnapshot() diff --git a/web/app/components/base/toast/index.tsx b/web/app/components/base/toast/index.tsx index b1b9ffe8c4..7c53acdc05 100644 --- a/web/app/components/base/toast/index.tsx +++ b/web/app/components/base/toast/index.tsx @@ -63,7 +63,7 @@ const Toast = ({ {type === 'warning' &&
-
+
{message}
{children &&
{children} @@ -71,7 +71,7 @@ const Toast = ({ }
- +
diff --git a/web/app/components/datasets/documents/detail/new-segment-modal.tsx b/web/app/components/datasets/documents/detail/new-segment-modal.tsx index dae9cf19fb..4c51779f9c 100644 --- a/web/app/components/datasets/documents/detail/new-segment-modal.tsx +++ b/web/app/components/datasets/documents/detail/new-segment-modal.tsx @@ -135,7 +135,7 @@ const NewSegmentModal: FC = ({
setKeywords(newKeywords)} />
-
+
diff --git a/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx b/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx index 44b63cf7dd..f1126e09c8 100644 --- a/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx +++ b/web/app/components/plugins/install-plugin/install-from-marketplace/index.tsx @@ -41,12 +41,14 @@ const InstallFromMarketplace: React.FC = ({ // TODO: check installed in beta version. const getTitle = useCallback(() => { + if (isBundle && step === InstallStep.installed) + return t(`${i18nPrefix}.installComplete`) if (step === InstallStep.installed) return t(`${i18nPrefix}.installedSuccessfully`) if (step === InstallStep.installFailed) return t(`${i18nPrefix}.installFailed`) return t(`${i18nPrefix}.installPlugin`) - }, [step, t]) + }, [isBundle, step, t]) const handleInstalled = useCallback(() => { setStep(InstallStep.installed) @@ -55,7 +57,7 @@ const InstallFromMarketplace: React.FC = ({ updateModelProviders() if (PluginType.tool.includes(manifest.category)) invalidateAllToolProviders() - }, [invalidateAllToolProviders, invalidateInstalledPluginList, manifest.category, updateModelProviders]) + }, [invalidateAllToolProviders, invalidateInstalledPluginList, manifest, updateModelProviders]) const handleFailed = useCallback((errorMsg?: string) => { setStep(InstallStep.installFailed) @@ -76,17 +78,6 @@ const InstallFromMarketplace: React.FC = ({ {getTitle()}
- { - step === InstallStep.readyToInstall && ( - - ) - } { isBundle ? ( = ({ onStepChange={setStep} onClose={onClose} allPlugins={dependencies!} + isFromMarketPlace /> - ) : ([InstallStep.installed, InstallStep.installFailed].includes(step)) && ( - + ) : (<> + { + step === InstallStep.readyToInstall && ( + + )} + { + [InstallStep.installed, InstallStep.installFailed].includes(step) && ( + + ) + } + ) } - + ) } diff --git a/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx b/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx index 596dc1c05e..cc7303a4c4 100644 --- a/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx +++ b/web/app/components/plugins/install-plugin/install-from-marketplace/steps/install.tsx @@ -94,7 +94,7 @@ const Installed: FC = ({ ) }) - }, [payload.latest_version, supportCheckInstalled]) + }, [payload.latest_version, payload.version, supportCheckInstalled]) return ( <> diff --git a/web/app/components/plugins/marketplace/context.tsx b/web/app/components/plugins/marketplace/context.tsx index 4b692cac28..57e3dae420 100644 --- a/web/app/components/plugins/marketplace/context.tsx +++ b/web/app/components/plugins/marketplace/context.tsx @@ -6,6 +6,7 @@ import type { import { useCallback, useEffect, + useMemo, useRef, useState, } from 'react' @@ -30,6 +31,7 @@ import { useMarketplacePlugins, } from './hooks' import { getMarketplaceListCondition } from './utils' +import { useInstalledPluginList } from '@/service/use-plugins' export type MarketplaceContextValue = { intersected: boolean @@ -74,6 +76,7 @@ export const MarketplaceContext = createContext({ type MarketplaceContextProviderProps = { children: ReactNode searchParams?: SearchParams + shouldExclude?: boolean } export function useMarketplaceContext(selector: (value: MarketplaceContextValue) => any) { @@ -83,7 +86,13 @@ export function useMarketplaceContext(selector: (value: MarketplaceContextValue) export const MarketplaceContextProvider = ({ children, searchParams, + shouldExclude, }: MarketplaceContextProviderProps) => { + const { data, isSuccess } = useInstalledPluginList(!shouldExclude) + const exclude = useMemo(() => { + if (shouldExclude) + return data?.plugins.map(plugin => plugin.plugin_id) + }, [data?.plugins, shouldExclude]) const queryFromSearchParams = searchParams?.q || '' const tagsFromSearchParams = searchParams?.tags ? getValidTagKeys(searchParams.tags.split(',')) : [] const hasValidTags = !!tagsFromSearchParams.length @@ -125,8 +134,15 @@ export const MarketplaceContextProvider = ({ }) history.pushState({}, '', `/${searchParams?.language ? `?language=${searchParams?.language}` : ''}`) } + else { + if (shouldExclude && isSuccess) { + queryMarketplaceCollectionsAndPlugins({ + exclude, + }) + } + } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [queryPlugins]) + }, [queryPlugins, queryMarketplaceCollectionsAndPlugins, isSuccess, exclude]) const handleSearchPluginTextChange = useCallback((text: string) => { setSearchPluginText(text) @@ -136,6 +152,7 @@ export const MarketplaceContextProvider = ({ queryMarketplaceCollectionsAndPlugins({ category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current, condition: getMarketplaceListCondition(activePluginTypeRef.current), + exclude, }) resetPlugins() @@ -148,8 +165,9 @@ export const MarketplaceContextProvider = ({ tags: filterPluginTagsRef.current, sortBy: sortRef.current.sortBy, sortOrder: sortRef.current.sortOrder, + exclude, }) - }, [queryPluginsWithDebounced, queryMarketplaceCollectionsAndPlugins, resetPlugins]) + }, [queryPluginsWithDebounced, queryMarketplaceCollectionsAndPlugins, resetPlugins, exclude]) const handleFilterPluginTagsChange = useCallback((tags: string[]) => { setFilterPluginTags(tags) @@ -159,6 +177,7 @@ export const MarketplaceContextProvider = ({ queryMarketplaceCollectionsAndPlugins({ category: activePluginTypeRef.current === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : activePluginTypeRef.current, condition: getMarketplaceListCondition(activePluginTypeRef.current), + exclude, }) resetPlugins() @@ -171,8 +190,9 @@ export const MarketplaceContextProvider = ({ tags, sortBy: sortRef.current.sortBy, sortOrder: sortRef.current.sortOrder, + exclude, }) - }, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins]) + }, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins, exclude]) const handleActivePluginTypeChange = useCallback((type: string) => { setActivePluginType(type) @@ -182,6 +202,7 @@ export const MarketplaceContextProvider = ({ queryMarketplaceCollectionsAndPlugins({ category: type === PLUGIN_TYPE_SEARCH_MAP.all ? undefined : type, condition: getMarketplaceListCondition(type), + exclude, }) resetPlugins() @@ -194,8 +215,9 @@ export const MarketplaceContextProvider = ({ tags: filterPluginTagsRef.current, sortBy: sortRef.current.sortBy, sortOrder: sortRef.current.sortOrder, + exclude, }) - }, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins]) + }, [queryPlugins, resetPlugins, queryMarketplaceCollectionsAndPlugins, exclude]) const handleSortChange = useCallback((sort: PluginsSort) => { setSort(sort) @@ -207,8 +229,9 @@ export const MarketplaceContextProvider = ({ tags: filterPluginTagsRef.current, sortBy: sortRef.current.sortBy, sortOrder: sortRef.current.sortOrder, + exclude, }) - }, [queryPlugins]) + }, [queryPlugins, exclude]) return ( { const [isLoading, setIsLoading] = useState(false) @@ -55,7 +57,7 @@ export const useMarketplacePlugins = () => { mutate(pluginsSearchParams) }, [mutate]) - const { run: queryPluginsWithDebounced } = useDebounceFn((pluginsSearchParams) => { + const { run: queryPluginsWithDebounced } = useDebounceFn((pluginsSearchParams: PluginsSearchParams) => { mutate(pluginsSearchParams) }, { wait: 500, diff --git a/web/app/components/plugins/marketplace/index.tsx b/web/app/components/plugins/marketplace/index.tsx index 5afb8c31ae..9f8bbb2e76 100644 --- a/web/app/components/plugins/marketplace/index.tsx +++ b/web/app/components/plugins/marketplace/index.tsx @@ -11,18 +11,26 @@ import { TanstackQueryIniter } from '@/context/query-client' type MarketplaceProps = { locale: string showInstallButton?: boolean + shouldExclude?: boolean searchParams?: SearchParams } const Marketplace = async ({ locale, showInstallButton = true, + shouldExclude, searchParams, }: MarketplaceProps) => { - const { marketplaceCollections, marketplaceCollectionPluginsMap } = await getMarketplaceCollectionsAndPlugins() + let marketplaceCollections: any = [] + let marketplaceCollectionPluginsMap = {} + if (!shouldExclude) { + const marketplaceCollectionsAndPluginsData = await getMarketplaceCollectionsAndPlugins() + marketplaceCollections = marketplaceCollectionsAndPluginsData.marketplaceCollections + marketplaceCollectionPluginsMap = marketplaceCollectionsAndPluginsData.marketplaceCollectionPluginsMap + } return ( - + diff --git a/web/app/components/plugins/marketplace/types.ts b/web/app/components/plugins/marketplace/types.ts index 58424d6c68..844ced0e00 100644 --- a/web/app/components/plugins/marketplace/types.ts +++ b/web/app/components/plugins/marketplace/types.ts @@ -27,6 +27,7 @@ export type PluginsSearchParams = { sortOrder?: string category?: string tags?: string[] + exclude?: string[] } export type PluginsSort = { @@ -37,6 +38,7 @@ export type PluginsSort = { export type CollectionsAndPluginsSearchParams = { category?: string condition?: string + exclude?: string[] } export type SearchParams = { diff --git a/web/app/components/plugins/marketplace/utils.ts b/web/app/components/plugins/marketplace/utils.ts index c9f77318f7..7252b9d315 100644 --- a/web/app/components/plugins/marketplace/utils.ts +++ b/web/app/components/plugins/marketplace/utils.ts @@ -3,7 +3,6 @@ import { PluginType } from '@/app/components/plugins/types' import type { CollectionsAndPluginsSearchParams, MarketplaceCollection, - PluginsSearchParams, } from '@/app/components/plugins/marketplace/types' import { MARKETPLACE_API_PREFIX } from '@/config' @@ -22,10 +21,18 @@ export const getMarketplaceCollectionsAndPlugins = async (query?: CollectionsAnd const marketplaceCollectionsDataJson = await marketplaceCollectionsData.json() marketplaceCollections = marketplaceCollectionsDataJson.data.collections await Promise.all(marketplaceCollections.map(async (collection: MarketplaceCollection) => { - let url = `${MARKETPLACE_API_PREFIX}/collections/${collection.name}/plugins?page=1&page_size=100` - if (query?.category) - url += `&category=${query.category}` - const marketplaceCollectionPluginsData = await globalThis.fetch(url, { cache: 'no-store' }) + const url = `${MARKETPLACE_API_PREFIX}/collections/${collection.name}/plugins` + const marketplaceCollectionPluginsData = await globalThis.fetch( + url, + { + cache: 'no-store', + method: 'POST', + body: JSON.stringify({ + category: query?.category, + exclude: query?.exclude, + }), + }, + ) const marketplaceCollectionPluginsDataJson = await marketplaceCollectionPluginsData.json() const plugins = marketplaceCollectionPluginsDataJson.data.plugins.map((plugin: Plugin) => { return { @@ -49,45 +56,6 @@ export const getMarketplaceCollectionsAndPlugins = async (query?: CollectionsAnd } } -export const getMarketplacePlugins = async (query: PluginsSearchParams) => { - let marketplacePlugins = [] as Plugin[] - try { - const marketplacePluginsData = await globalThis.fetch( - `${MARKETPLACE_API_PREFIX}/plugins/search/basic`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - page: 1, - page_size: 10, - query: query.query, - sort_by: query.sortBy, - sort_order: query.sortOrder, - category: query.category, - tags: query.tags, - }), - }, - ) - const marketplacePluginsDataJson = await marketplacePluginsData.json() - marketplacePlugins = marketplacePluginsDataJson.data.plugins.map((plugin: Plugin) => { - return { - ...plugin, - icon: getPluginIconInMarketplace(plugin), - } - }) - } - // eslint-disable-next-line unused-imports/no-unused-vars - catch (e) { - marketplacePlugins = [] - } - - return { - marketplacePlugins, - } -} - export const getMarketplaceListCondition = (pluginType: string) => { if (pluginType === PluginType.tool) return 'category=tool' diff --git a/web/app/components/plugins/plugin-detail-panel/detail-header.tsx b/web/app/components/plugins/plugin-detail-panel/detail-header.tsx index 767366938f..75239a424f 100644 --- a/web/app/components/plugins/plugin-detail-panel/detail-header.tsx +++ b/web/app/components/plugins/plugin-detail-panel/detail-header.tsx @@ -201,7 +201,15 @@ const DetailHeader = ({ } /> {(hasNewVersion || isFromGitHub) && ( - + )}
diff --git a/web/app/components/plugins/plugin-page/index.tsx b/web/app/components/plugins/plugin-page/index.tsx index 486f4f47ce..f5b354effc 100644 --- a/web/app/components/plugins/plugin-page/index.tsx +++ b/web/app/components/plugins/plugin-page/index.tsx @@ -28,13 +28,15 @@ import { useRouter, useSearchParams, } from 'next/navigation' +import type { Dependency } from '../types' import type { PluginDeclaration, PluginManifestInMarket } from '../types' import { sleep } from '@/utils' -import { fetchManifestFromMarketPlace } from '@/service/plugins' +import { fetchBundleInfoFromMarketPlace, fetchManifestFromMarketPlace } from '@/service/plugins' import { marketplaceApiPrefix } from '@/config' import { SUPPORT_INSTALL_LOCAL_FILE_EXTENSIONS } from '@/config' const PACKAGE_IDS_KEY = 'package-ids' +const BUNDLE_INFO_KEY = 'bundle-info' export type PluginPageProps = { plugins: React.ReactNode @@ -58,6 +60,18 @@ const PluginPage = ({ return '' } }, [searchParams]) + + const [dependencies, setDependencies] = useState([]) + const bundleInfo = useMemo(() => { + const info = searchParams.get(BUNDLE_INFO_KEY) + try { + return info ? JSON.parse(info) : undefined + } + catch (e) { + return undefined + } + }, [searchParams]) + const [isShowInstallFromMarketplace, { setTrue: showInstallFromMarketplace, setFalse: doHideInstallFromMarketplace, @@ -67,6 +81,7 @@ const PluginPage = ({ doHideInstallFromMarketplace() const url = new URL(window.location.href) url.searchParams.delete(PACKAGE_IDS_KEY) + url.searchParams.delete(BUNDLE_INFO_KEY) replace(url.toString()) } const [manifest, setManifest] = useState(null) @@ -76,16 +91,23 @@ const PluginPage = ({ await sleep(100) if (packageId) { const { data } = await fetchManifestFromMarketPlace(encodeURIComponent(packageId)) - const { plugin } = data + const { plugin, version } = data setManifest({ ...plugin, + version: version.version, icon: `${marketplaceApiPrefix}/plugins/${plugin.org}/${plugin.name}/icon`, }) showInstallFromMarketplace() + return + } + if (bundleInfo) { + const { data } = await fetchBundleInfoFromMarketPlace(bundleInfo) + setDependencies(data.version.dependencies) + showInstallFromMarketplace() } })() // eslint-disable-next-line react-hooks/exhaustive-deps - }, [packageId]) + }, [packageId, bundleInfo]) const { canManagement, @@ -210,6 +232,8 @@ const PluginPage = ({ diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts index 5de8a6d2df..1ab127ec55 100644 --- a/web/app/components/plugins/types.ts +++ b/web/app/components/plugins/types.ts @@ -79,7 +79,7 @@ export type PluginManifestInMarket = { icon: string label: Record category: PluginType - version: string // TODO: wait api return current plugin version + version: string // conbine the other place to it latest_version: string brief: Record introduction: string diff --git a/web/app/components/plugins/update-plugin/from-market-place.tsx b/web/app/components/plugins/update-plugin/from-market-place.tsx index 071b143115..e4abd32aff 100644 --- a/web/app/components/plugins/update-plugin/from-market-place.tsx +++ b/web/app/components/plugins/update-plugin/from-market-place.tsx @@ -89,6 +89,7 @@ const UpdatePluginModal: FC = ({ }) onSave() } + // eslint-disable-next-line unused-imports/no-unused-vars catch (e) { setUploadStep(UploadStep.notStarted) } diff --git a/web/app/components/tools/marketplace/hooks.ts b/web/app/components/tools/marketplace/hooks.ts index 3aec42be75..e29920ab29 100644 --- a/web/app/components/tools/marketplace/hooks.ts +++ b/web/app/components/tools/marketplace/hooks.ts @@ -1,5 +1,6 @@ import { useEffect, + useMemo, } from 'react' import { useMarketplaceCollectionsAndPlugins, @@ -7,8 +8,14 @@ import { } from '@/app/components/plugins/marketplace/hooks' import { PluginType } from '@/app/components/plugins/types' import { getMarketplaceListCondition } from '@/app/components/plugins/marketplace/utils' +import { useAllToolProviders } from '@/service/use-tools' export const useMarketplace = (searchPluginText: string, filterPluginTags: string[]) => { + const { data: toolProvidersData, isSuccess } = useAllToolProviders() + const exclude = useMemo(() => { + if (isSuccess) + return toolProvidersData?.filter(toolProvider => !!toolProvider.plugin_id).map(toolProvider => toolProvider.plugin_id!) + }, [isSuccess, toolProvidersData]) const { isLoading, marketplaceCollections, @@ -24,12 +31,13 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin } = useMarketplacePlugins() useEffect(() => { - if (searchPluginText || filterPluginTags.length) { + if ((searchPluginText || filterPluginTags.length) && isSuccess) { if (searchPluginText) { queryPluginsWithDebounced({ category: PluginType.tool, query: searchPluginText, tags: filterPluginTags, + exclude, }) return } @@ -37,16 +45,20 @@ export const useMarketplace = (searchPluginText: string, filterPluginTags: strin category: PluginType.tool, query: searchPluginText, tags: filterPluginTags, + exclude, }) } else { - queryMarketplaceCollectionsAndPlugins({ - category: PluginType.tool, - condition: getMarketplaceListCondition(PluginType.tool), - }) - resetPlugins() + if (isSuccess) { + queryMarketplaceCollectionsAndPlugins({ + category: PluginType.tool, + condition: getMarketplaceListCondition(PluginType.tool), + exclude, + }) + resetPlugins() + } } - }, [searchPluginText, filterPluginTags, queryPlugins, queryMarketplaceCollectionsAndPlugins, queryPluginsWithDebounced, resetPlugins]) + }, [searchPluginText, filterPluginTags, queryPlugins, queryMarketplaceCollectionsAndPlugins, queryPluginsWithDebounced, resetPlugins, exclude, isSuccess]) return { isLoading: isLoading || isPluginsLoading, diff --git a/web/app/components/workflow/index.tsx b/web/app/components/workflow/index.tsx index 2eccb38e47..ecd35876e7 100644 --- a/web/app/components/workflow/index.tsx +++ b/web/app/components/workflow/index.tsx @@ -72,7 +72,6 @@ import SyncingDataModal from './syncing-data-modal' import UpdateDSLModal from './update-dsl-modal' import DSLExportConfirmModal from './dsl-export-confirm-modal' import LimitTips from './limit-tips' -import PluginDependency from './plugin-dependency' import { useStore, useWorkflowStore, @@ -327,7 +326,6 @@ const Workflow: FC = memo(({ /> ) } - = ({ const index = (() => { if (match) - return parseInt(match[1]!) + 1 + return Number.parseInt(match[1]!) + 1 return 1 })() diff --git a/web/app/components/workflow/nodes/code/panel.tsx b/web/app/components/workflow/nodes/code/panel.tsx index a0027daf53..f20a9c60d6 100644 --- a/web/app/components/workflow/nodes/code/panel.tsx +++ b/web/app/components/workflow/nodes/code/panel.tsx @@ -13,7 +13,7 @@ import Field from '@/app/components/workflow/nodes/_base/components/field' import Split from '@/app/components/workflow/nodes/_base/components/split' import CodeEditor from '@/app/components/workflow/nodes/_base/components/editor/code-editor' import TypeSelector from '@/app/components/workflow/nodes/_base/components/selector' -import { type NodePanelProps } from '@/app/components/workflow/types' +import type { NodePanelProps } from '@/app/components/workflow/types' import BeforeRunForm from '@/app/components/workflow/nodes/_base/components/before-run-form' import ResultPanel from '@/app/components/workflow/run/result-panel' const i18nPrefix = 'workflow.nodes.code' diff --git a/web/app/components/workflow/plugin-dependency/index.tsx b/web/app/components/workflow/plugin-dependency/index.tsx index 13f138114b..8fd87b9627 100644 --- a/web/app/components/workflow/plugin-dependency/index.tsx +++ b/web/app/components/workflow/plugin-dependency/index.tsx @@ -1,23 +1,43 @@ -import { useCallback } from 'react' +import { + useCallback, + useState, +} from 'react' +import { useTranslation } from 'react-i18next' import { useStore } from './store' -import InstallBundle from '@/app/components/plugins/install-plugin/install-bundle' +import ReadyToInstall from '@/app/components/plugins/install-plugin/install-bundle/ready-to-install' +import { InstallStep } from '@/app/components/plugins/types' +const i18nPrefix = 'plugin.installModal' const PluginDependency = () => { const dependencies = useStore(s => s.dependencies) - const handleCancelInstallBundle = useCallback(() => { - const { setDependencies } = useStore.getState() - setDependencies([]) - }, []) + const [step, setStep] = useState(InstallStep.readyToInstall) + + const { t } = useTranslation() + const getTitle = useCallback(() => { + if (step === InstallStep.uploadFailed) + return t(`${i18nPrefix}.uploadFailed`) + if (step === InstallStep.installed) + return t(`${i18nPrefix}.installComplete`) + + return t(`${i18nPrefix}.installPlugin`) + }, [step, t]) if (!dependencies.length) return null return (
- +
+ {getTitle()} +
+
+ {}} />
) diff --git a/web/app/components/workflow/update-dsl-modal.tsx b/web/app/components/workflow/update-dsl-modal.tsx index b956ed43da..a750ebe21e 100644 --- a/web/app/components/workflow/update-dsl-modal.tsx +++ b/web/app/components/workflow/update-dsl-modal.tsx @@ -38,7 +38,8 @@ import { ToastContext } from '@/app/components/base/toast' import { useEventEmitterContextContext } from '@/context/event-emitter' import { useStore as useAppStore } from '@/app/components/app/store' import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' -import { useMutationCheckDependenciesBeforeImportDSL } from '@/service/use-plugins' +import { useStore as usePluginDependencyStore } from '@/app/components/workflow/plugin-dependency/store' +import PluginDependency from '@/app/components/workflow/plugin-dependency' type UpdateDSLModalProps = { onCancel: () => void @@ -58,7 +59,6 @@ const UpdateDSLModal = ({ const [fileContent, setFileContent] = useState() const [loading, setLoading] = useState(false) const { eventEmitter } = useEventEmitterContextContext() - const { mutateAsync, mutate } = useMutationCheckDependenciesBeforeImportDSL() const [show, setShow] = useState(true) const [showErrorModal, setShowErrorModal] = useState(false) const [versions, setVersions] = useState<{ importedVersion: string; systemVersion: string }>() @@ -81,7 +81,7 @@ const UpdateDSLModal = ({ setFileContent('') } - const handleWorkflowUpdate = async (app_id: string) => { + const handleWorkflowUpdate = useCallback(async (app_id: string) => { const { graph, features, @@ -124,7 +124,7 @@ const UpdateDSLModal = ({ hash, }, } as any) - } + }, [eventEmitter]) const isCreatingRef = useRef(false) const handleImport: MouseEventHandler = useCallback(async () => { @@ -136,36 +136,42 @@ const UpdateDSLModal = ({ try { if (appDetail && fileContent) { setLoading(true) - const { - graph, - features, - hash, - } = await updateWorkflowDraftFromDSL(appDetail.id, fileContent) - await mutateAsync({ dslString: fileContent }) - const { nodes, edges, viewport } = graph - const newFeatures = { - file: { - image: { - enabled: !!features.file_upload?.image?.enabled, - number_limits: features.file_upload?.image?.number_limits || 3, - transfer_methods: features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'], - }, - enabled: !!(features.file_upload?.enabled || features.file_upload?.image?.enabled), - allowed_file_types: features.file_upload?.allowed_file_types || [SupportUploadFileTypes.image], - allowed_file_extensions: features.file_upload?.allowed_file_extensions || FILE_EXTS[SupportUploadFileTypes.image].map(ext => `.${ext}`), - allowed_file_upload_methods: features.file_upload?.allowed_file_upload_methods || features.file_upload?.image?.transfer_methods || ['local_file', 'remote_url'], - number_limits: features.file_upload?.number_limits || features.file_upload?.image?.number_limits || 3, - }, - opening: { - enabled: !!features.opening_statement, - opening_statement: features.opening_statement, - suggested_questions: features.suggested_questions, - }, - suggested: features.suggested_questions_after_answer || { enabled: false }, - speech2text: features.speech_to_text || { enabled: false }, - text2speech: features.text_to_speech || { enabled: false }, - citation: features.retriever_resource || { enabled: false }, - moderation: features.sensitive_word_avoidance || { enabled: false }, + const response = await importDSL({ mode: DSLImportMode.YAML_CONTENT, yaml_content: fileContent, app_id: appDetail.id }) + const { id, status, app_id, imported_dsl_version, current_dsl_version, leaked_dependencies } = response + if (leaked_dependencies?.length) { + const { setDependencies } = usePluginDependencyStore.getState() + setDependencies(leaked_dependencies) + } + if (status === DSLImportStatus.COMPLETED || status === DSLImportStatus.COMPLETED_WITH_WARNINGS) { + if (!app_id) { + notify({ type: 'error', message: t('workflow.common.importFailure') }) + return + } + handleWorkflowUpdate(app_id) + if (onImport) + onImport() + notify({ + type: status === DSLImportStatus.COMPLETED ? 'success' : 'warning', + message: t(status === DSLImportStatus.COMPLETED ? 'workflow.common.importSuccess' : 'workflow.common.importWarning'), + children: status === DSLImportStatus.COMPLETED_WITH_WARNINGS && t('workflow.common.importWarningDetails'), + }) + setLoading(false) + onCancel() + } + else if (status === DSLImportStatus.PENDING) { + setShow(false) + setTimeout(() => { + setShowErrorModal(true) + }, 300) + setVersions({ + importedVersion: imported_dsl_version ?? '', + systemVersion: current_dsl_version ?? '', + }) + setImportId(id) + } + else { + setLoading(false) + notify({ type: 'error', message: t('workflow.common.importFailure') }) } } } @@ -174,7 +180,7 @@ const UpdateDSLModal = ({ notify({ type: 'error', message: t('workflow.common.importFailure') }) } isCreatingRef.current = false - }, [currentFile, fileContent, onCancel, notify, t, eventEmitter, appDetail, onImport, mutateAsync]) + }, [currentFile, fileContent, onCancel, notify, t, appDetail, onImport, handleWorkflowUpdate]) const onUpdateDSLConfirm: MouseEventHandler = async () => { try { @@ -222,12 +228,12 @@ const UpdateDSLModal = ({ -
+
- +
-
+
{t('workflow.common.importDSLTip')}
diff --git a/web/models/app.ts b/web/models/app.ts index acb1c09622..81a8575dd8 100644 --- a/web/models/app.ts +++ b/web/models/app.ts @@ -1,5 +1,6 @@ import type { LangFuseConfig, LangSmithConfig, TracingProvider } from '@/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/type' import type { App, AppSSO, AppTemplate, SiteConfig } from '@/types/app' +import type { Dependency } from '@/app/components/plugins/types' /* export type App = { id: string @@ -87,6 +88,7 @@ export type DSLImportResponse = { current_dsl_version?: string imported_dsl_version?: string error: string + leaked_dependencies: Dependency[] } export type AppSSOResponse = { enabled: AppSSO['enable_sso'] } diff --git a/web/service/plugins.ts b/web/service/plugins.ts index 28bf6c44f4..2857948860 100644 --- a/web/service/plugins.ts +++ b/web/service/plugins.ts @@ -1,6 +1,7 @@ import type { Fetcher } from 'swr' import { get, getMarketplace, post, upload } from './base' import type { + Dependency, InstallPackageResponse, Permissions, PluginDeclaration, @@ -63,7 +64,15 @@ export const fetchManifest = async (uniqueIdentifier: string) => { } export const fetchManifestFromMarketPlace = async (uniqueIdentifier: string) => { - return getMarketplace<{ data: { plugin: PluginManifestInMarket } }>(`/plugins/identifier?unique_identifier=${uniqueIdentifier}`) + return getMarketplace<{ data: { plugin: PluginManifestInMarket, version: { version: string } } }>(`/plugins/identifier?unique_identifier=${uniqueIdentifier}`) +} + +export const fetchBundleInfoFromMarketPlace = async ({ + org, + name, + version, +}: Record) => { + return getMarketplace<{ data: { version: { dependencies: Dependency[] } } }>(`/bundles/${org}/${name}/${version}`) } export const fetchMarketplaceCollections: Fetcher = ({ url }) => { diff --git a/web/service/use-plugins.ts b/web/service/use-plugins.ts index 08469023d6..9bdf63c5c5 100644 --- a/web/service/use-plugins.ts +++ b/web/service/use-plugins.ts @@ -24,16 +24,17 @@ import { useQuery, useQueryClient, } from '@tanstack/react-query' -import { useStore as usePluginDependencyStore } from '@/app/components/workflow/plugin-dependency/store' import { useInvalidateAllBuiltInTools } from './use-tools' const NAME_SPACE = 'plugins' const useInstalledPluginListKey = [NAME_SPACE, 'installedPluginList'] -export const useInstalledPluginList = () => { +export const useInstalledPluginList = (disable?: boolean) => { return useQuery({ queryKey: useInstalledPluginListKey, queryFn: () => get('/workspaces/current/plugin/list'), + enabled: !disable, + initialData: !disable ? undefined : { plugins: [] }, }) } @@ -110,6 +111,7 @@ export const useUploadGitHub = (payload: { queryFn: () => post('/workspaces/current/plugin/upload/github', { body: payload, }), + retry: 0, }) } @@ -225,6 +227,7 @@ export const useMutationPluginsFromMarketplace = () => { sortOrder, category, tags, + exclude, } = pluginsSearchParams return postMarketplace<{ data: PluginsFromMarketplaceResponse }>('/plugins/search/basic', { body: { @@ -235,6 +238,7 @@ export const useMutationPluginsFromMarketplace = () => { sort_order: sortOrder, category: category !== 'all' ? category : '', tags, + exclude, }, }) }, @@ -323,36 +327,6 @@ export const useMutationClearAllTaskPlugin = () => { }) } -export const useMutationCheckDependenciesBeforeImportDSL = () => { - const mutation = useMutation({ - mutationFn: ({ dslString, url }: { dslString?: string, url?: string }) => { - if (url) { - return post<{ leaked: Dependency[] }>( - '/apps/import/url/dependencies/check', - { - body: { - url, - }, - }, - ) - } - return post<{ leaked: Dependency[] }>( - '/apps/import/dependencies/check', - { - body: { - data: dslString, - }, - }) - }, - onSuccess: (data) => { - const { setDependencies } = usePluginDependencyStore.getState() - setDependencies(data.leaked || []) - }, - }) - - return mutation -} - export const useDownloadPlugin = (info: { organization: string; pluginName: string; version: string }, needDownload: boolean) => { return useQuery({ queryKey: [NAME_SPACE, 'downloadPlugin', info],