From 66be03f6229bbb1f0185e0312a93a5c228b1f9ee Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 1 Nov 2024 14:02:10 +0800 Subject: [PATCH 001/195] fix: search tools ui and some ui problem --- .../market-place-plugin/list.tsx | 12 ++++++---- .../workflow/block-selector/tool-picker.tsx | 22 +++++++++++-------- web/i18n/en-US/plugin.ts | 1 + web/i18n/zh-Hans/plugin.ts | 1 + 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/web/app/components/workflow/block-selector/market-place-plugin/list.tsx b/web/app/components/workflow/block-selector/market-place-plugin/list.tsx index 3da4e04af5..6c82bd5c0c 100644 --- a/web/app/components/workflow/block-selector/market-place-plugin/list.tsx +++ b/web/app/components/workflow/block-selector/market-place-plugin/list.tsx @@ -1,5 +1,5 @@ 'use client' -import React, { forwardRef, useImperativeHandle, useMemo, useRef } from 'react' +import React, { forwardRef, useEffect, useImperativeHandle, useMemo, useRef } from 'react' import { useTranslation } from 'react-i18next' import useStickyScroll, { ScrollPosition } from '../use-sticky-scroll' import Item from './item' @@ -30,7 +30,6 @@ const List = ({ wrapElemRef, nextToStickyELemRef, }) - const stickyClassName = useMemo(() => { switch (scrollPosition) { case ScrollPosition.aboveTheWrap: @@ -38,7 +37,7 @@ const List = ({ case ScrollPosition.showing: return 'bottom-0 pt-3 pb-1' case ScrollPosition.belowTheWrap: - return 'bottom-0 items-center rounded-b-xl border-t border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg cursor-pointer' + return 'bottom-0 items-center rounded-b-xl border-t border-[0.5px] border-components-panel-border bg-components-panel-bg-blur shadow-lg rounded-b-lg cursor-pointer' } }, [scrollPosition]) @@ -46,6 +45,11 @@ const List = ({ handleScroll, })) + useEffect(() => { + handleScroll() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [list]) + const handleHeadClick = () => { if (scrollPosition === ScrollPosition.belowTheWrap) { nextToStickyELemRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }) @@ -57,7 +61,7 @@ const List = ({ if (hasSearchText) { return ( diff --git a/web/app/components/workflow/block-selector/tool-picker.tsx b/web/app/components/workflow/block-selector/tool-picker.tsx index 9ae8d625f9..f06bef73b9 100644 --- a/web/app/components/workflow/block-selector/tool-picker.tsx +++ b/web/app/components/workflow/block-selector/tool-picker.tsx @@ -20,6 +20,7 @@ import { } from '@/service/tools' import type { BlockEnum, ToolWithProvider } from '@/app/components/workflow/types' import SearchBox from '@/app/components/plugins/marketplace/search-box' +import { useTranslation } from 'react-i18next' type Props = { disabled: boolean @@ -42,6 +43,7 @@ const ToolPicker: FC = ({ onSelect, supportAddCustomTool, }) => { + const { t } = useTranslation() const [searchText, setSearchText] = useState('') const [buildInTools, setBuildInTools] = useState([]) @@ -83,15 +85,17 @@ const ToolPicker: FC = ({ -
- {}} - size='small' - placeholder='Search tools...' - /> +
+
+ { }} + size='small' + placeholder={t('plugin.searchTools')!} + /> +
Date: Fri, 1 Nov 2024 14:41:27 +0800 Subject: [PATCH 002/195] feat: can choose tool in agent page --- .../config/agent/agent-tools/index.tsx | 35 +++++++++++++++---- .../workflow/block-selector/all-tools.tsx | 9 +++-- .../workflow/block-selector/tool-picker.tsx | 1 - 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx index b66f331f5b..bbc352b9b4 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx @@ -27,10 +27,12 @@ import { MAX_TOOLS_NUM } from '@/config' import { AlertTriangle } from '@/app/components/base/icons/src/vender/solid/alertsAndFeedback' import Tooltip from '@/app/components/base/tooltip' import { DefaultToolIcon } from '@/app/components/base/icons/src/public/other' -import AddToolModal from '@/app/components/tools/add-tool-modal' +// import AddToolModal from '@/app/components/tools/add-tool-modal' import ConfigCredential from '@/app/components/tools/setting/build-in/config-credentials' import { updateBuiltInToolCredential } from '@/service/tools' import cn from '@/utils/classnames' +import ToolPicker from '@/app/components/workflow/block-selector/tool-picker' +import type { ToolDefaultValue } from '@/app/components/workflow/block-selector/types' type AgentToolWithMoreInfo = AgentTool & { icon: any; collection?: Collection } | null const AgentTools: FC = () => { @@ -81,6 +83,21 @@ const AgentTools: FC = () => { const [isDeleting, setIsDeleting] = useState(-1) + const handleSelectTool = (tool: ToolDefaultValue) => { + const newModelConfig = produce(modelConfig, (draft) => { + draft.agentConfig.tools.push({ + provider_id: tool.provider_id, + provider_type: tool.provider_type as CollectionType, + provider_name: tool.provider_name, + tool_name: tool.tool_name, + tool_label: tool.tool_label, + tool_parameters: {}, + enabled: true, + }) + }) + setModelConfig(newModelConfig) + } + return ( <> { {tools.length < MAX_TOOLS_NUM && ( <>
- setIsShowChooseTool(true)} /> + } + isShow={isShowChooseTool} + onShowChange={setIsShowChooseTool} + disabled={false} + supportAddCustomTool + onSelect={handleSelectTool} + /> )}
@@ -125,8 +149,8 @@ const AgentTools: FC = () => { {item.isDeleted && } {!item.isDeleted && (
- {typeof item.icon === 'string' &&
} - {typeof item.icon !== 'string' && } + {typeof item.icon === 'string' &&
} + {typeof item.icon !== 'string' && }
)}
{ ))}
- {isShowChooseTool && ( - setIsShowChooseTool(false)} /> - )} {isShowSettingTool && ( {supportAddCustomTool && ( - - - +
+
+ + + +
)}
= ({ onOpenChange={onShowChange} > {trigger} From 245bb02c886ab79e50c9344f6ce3ae8e2399641b Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 1 Nov 2024 14:51:35 +0800 Subject: [PATCH 003/195] chore: add on start to install --- .../install-from-local-package/steps/install.tsx | 6 +++++- .../install-from-marketplace/steps/install.tsx | 5 ++++- 2 files changed, 9 insertions(+), 2 deletions(-) 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 index 08b21ad1ff..8461eeb425 100644 --- 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 @@ -13,10 +13,11 @@ import checkTaskStatus from '../../base/check-task-status' const i18nPrefix = 'plugin.installModal' -interface Props { +type Props = { uniqueIdentifier: string payload: PluginDeclaration onCancel: () => void + onStartToInstall?: () => void onInstalled: () => void onFailed: (message?: string) => void } @@ -25,6 +26,7 @@ const Installed: FC = ({ uniqueIdentifier, payload, onCancel, + onStartToInstall, onInstalled, onFailed, }) => { @@ -43,6 +45,8 @@ const Installed: FC = ({ const handleInstall = async () => { if (isInstalling) return setIsInstalling(true) + onStartToInstall?.() + try { const { all_installed: isInstalled, 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 8a6c5d3c0e..f1f5fecf93 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 @@ -18,6 +18,7 @@ type Props = { uniqueIdentifier: string payload: PluginManifestInMarket onCancel: () => void + onStartToInstall?: () => void onInstalled: () => void onFailed: (message?: string) => void } @@ -26,6 +27,7 @@ const Installed: FC = ({ uniqueIdentifier, payload, onCancel, + onStartToInstall, onInstalled, onFailed, }) => { @@ -43,6 +45,7 @@ const Installed: FC = ({ const handleInstall = async () => { if (isInstalling) return + onStartToInstall?.() setIsInstalling(true) try { @@ -90,7 +93,7 @@ const Installed: FC = ({ ) }) - }, [payload]) + }, [payload.latest_version, supportCheckInstalled]) return ( <> From c503e8ebc9fe451d35af0536c9ae1cffa05c64aa Mon Sep 17 00:00:00 2001 From: Yi Date: Fri, 1 Nov 2024 14:55:56 +0800 Subject: [PATCH 004/195] chore: install package from GitHub --- .../workplace-selector/index.tsx | 8 -- .../plugins/install-plugin/hooks.ts | 68 +++++++++++ .../install-from-github/index.tsx | 113 ++++++++---------- .../install-from-github/steps/loaded.tsx | 55 +++++++++ .../{setVersion.tsx => selectPackage.tsx} | 39 +++++- .../install-from-github/steps/setPackage.tsx | 53 -------- .../install-from-github/steps/setURL.tsx | 2 + .../plugins/install-plugin/utils.ts | 8 ++ web/app/components/plugins/types.ts | 9 +- web/service/plugins.ts | 23 +++- 10 files changed, 239 insertions(+), 139 deletions(-) create mode 100644 web/app/components/plugins/install-plugin/hooks.ts create mode 100644 web/app/components/plugins/install-plugin/install-from-github/steps/loaded.tsx rename web/app/components/plugins/install-plugin/install-from-github/steps/{setVersion.tsx => selectPackage.tsx} (57%) delete mode 100644 web/app/components/plugins/install-plugin/install-from-github/steps/setPackage.tsx diff --git a/web/app/components/header/account-dropdown/workplace-selector/index.tsx b/web/app/components/header/account-dropdown/workplace-selector/index.tsx index 4d21ccdb59..fd3a997436 100644 --- a/web/app/components/header/account-dropdown/workplace-selector/index.tsx +++ b/web/app/components/header/account-dropdown/workplace-selector/index.tsx @@ -6,15 +6,12 @@ import { RiArrowDownSLine } from '@remixicon/react' import cn from '@/utils/classnames' import { switchWorkspace } from '@/service/common' import { useWorkspacesContext } from '@/context/workspace-context' -import HeaderBillingBtn from '@/app/components/billing/header-billing-btn' -import { useProviderContext } from '@/context/provider-context' import { ToastContext } from '@/app/components/base/toast' const WorkplaceSelector = () => { const { t } = useTranslation() const { notify } = useContext(ToastContext) const { workspaces } = useWorkspacesContext() - const { enableBilling } = useProviderContext() const currentWorkspace = workspaces.find(v => v.current) const handleSwitchWorkspace = async (tenant_id: string) => { try { @@ -69,11 +66,6 @@ const WorkplaceSelector = () => {
handleSwitchWorkspace(workspace.id)}>
{workspace.name[0].toLocaleUpperCase()}
{workspace.name}
- {enableBilling && ( -
- -
- )}
)) } diff --git a/web/app/components/plugins/install-plugin/hooks.ts b/web/app/components/plugins/install-plugin/hooks.ts new file mode 100644 index 0000000000..2d4271e887 --- /dev/null +++ b/web/app/components/plugins/install-plugin/hooks.ts @@ -0,0 +1,68 @@ +import { useState } from 'react' +import Toast from '@/app/components/base/toast' +import { uploadGitHub } from '@/service/plugins' + +export const useGitHubReleases = () => { + const fetchReleases = async (owner: string, repo: string, setReleases: (releases: any) => void) => { + try { + const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/releases`) + if (!res.ok) throw new Error('Failed to fetch releases') + const data = await res.json() + + const formattedReleases = data.map((release: any) => ({ + tag_name: release.tag_name, + assets: release.assets.map((asset: any) => ({ + browser_download_url: asset.browser_download_url, + name: asset.name, + })), + })) + + setReleases(formattedReleases) + } + catch (error) { + Toast.notify({ + type: 'error', + message: 'Failed to fetch repository releases', + }) + } + } + + return { fetchReleases } +} + +export const useGitHubUpload = () => { + const [isLoading, setIsLoading] = useState(false) + const [error, setError] = useState(null) + + const handleUpload = async ( + repoUrl: string, + selectedVersion: string, + selectedPackage: string, + onSuccess?: (GitHubPackage: { manifest: any; uniqueIdentifier: string }) => void, + ) => { + setIsLoading(true) + setError(null) + + try { + const response = await uploadGitHub(repoUrl, selectedVersion, selectedPackage) + const GitHubPackage = { + manifest: response.manifest, + uniqueIdentifier: response.plugin_unique_identifier, + } + if (onSuccess) onSuccess(GitHubPackage) + return GitHubPackage + } + catch (error) { + setError('Error installing package') + Toast.notify({ + type: 'error', + message: 'Error installing package', + }) + } + finally { + setIsLoading(false) + } + } + + return { handleUpload, isLoading, error } +} diff --git a/web/app/components/plugins/install-plugin/install-from-github/index.tsx b/web/app/components/plugins/install-plugin/install-from-github/index.tsx index 06c4c0797a..890424bfed 100644 --- a/web/app/components/plugins/install-plugin/install-from-github/index.tsx +++ b/web/app/components/plugins/install-plugin/install-from-github/index.tsx @@ -3,13 +3,16 @@ import React, { useState } from 'react' import Modal from '@/app/components/base/modal' import type { Item } from '@/app/components/base/select' -import type { GitHubUrlInfo, InstallState } from '@/app/components/plugins/types' +import type { InstallState } from '@/app/components/plugins/types' +import { useGitHubReleases, useGitHubUpload } from '../hooks' +import { parseGitHubUrl } from '../utils' +import type { PluginDeclaration } from '../../types' import { InstallStepFromGitHub } from '../../types' import Toast from '@/app/components/base/toast' import SetURL from './steps/setURL' -import SetVersion from './steps/setVersion' -import SetPackage from './steps/setPackage' +import SelectPackage from './steps/selectPackage' import Installed from './steps/installed' +import Loaded from './steps/loaded' import { useTranslation } from 'react-i18next' type InstallFromGitHubProps = { @@ -25,6 +28,8 @@ const InstallFromGitHub: React.FC = ({ onClose }) => { selectedPackage: '', releases: [], }) + const [uniqueIdentifier, setUniqueIdentifier] = useState(null) + const [manifest, setManifest] = useState(null) const versions: Item[] = state.releases.map(release => ({ value: release.tag_name, @@ -36,41 +41,16 @@ const InstallFromGitHub: React.FC = ({ onClose }) => { .find(release => release.tag_name === state.selectedVersion) ?.assets .map(asset => ({ - value: asset.browser_download_url, + value: asset.name, name: asset.name, })) || []) : [] - const parseGitHubUrl = (url: string): GitHubUrlInfo => { - const githubUrlRegex = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/?$/ - const match = url.match(githubUrlRegex) - - if (match) { - return { - isValid: true, - owner: match[1], - repo: match[2], - } - } - - return { isValid: false } - } + const { isLoading, handleUpload, error } = useGitHubUpload() + const { fetchReleases } = useGitHubReleases() const handleInstall = async () => { - // try { - // const response = await installPackageFromGitHub({ repo: state.repoUrl, version: state.selectedVersion, package: state.selectedPackage }) - // if (response.plugin_unique_identifier) { - // setState(prevState => ({...prevState, step: InstallStep.installed})) - // console.log('Package installed:') - // } - // else { - // console.error('Failed to install package:') - // } - // } - // catch (error) { - // console.error('Error installing package:') - // } - setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.installed })) + } const handleNext = async () => { @@ -84,45 +64,48 @@ const InstallFromGitHub: React.FC = ({ onClose }) => { }) break } - try { - const res = await fetch(`https://api.github.com/repos/${owner}/${repo}/releases`) - if (!res.ok) - throw new Error('Failed to fetch releases') - const data = await res.json() - const formattedReleases = data.map((release: any) => ({ - tag_name: release.tag_name, - assets: release.assets.map((asset: any) => ({ - browser_download_url: asset.browser_download_url, - id: asset.id, - name: asset.name, - })), + await fetchReleases(owner, repo, (fetchedReleases) => { + setState(prevState => ({ + ...prevState, + releases: fetchedReleases, + step: InstallStepFromGitHub.selectPackage, })) - setState(prevState => ({ ...prevState, releases: formattedReleases, step: InstallStepFromGitHub.setVersion })) - } - catch (error) { + }) + break + } + case InstallStepFromGitHub.selectPackage: { + const repo = state.repoUrl.replace('https://github.com/', '') + if (error) { Toast.notify({ type: 'error', - message: 'Failed to fetch repository release', + message: error, + }) + } + else { + await handleUpload(repo, state.selectedVersion, state.selectedPackage, (GitHubPackage) => { + setManifest(GitHubPackage.manifest) + setUniqueIdentifier(GitHubPackage.uniqueIdentifier) + setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.loaded })) }) } break } - case InstallStepFromGitHub.setVersion: - setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.setPackage })) - break - case InstallStepFromGitHub.setPackage: + case InstallStepFromGitHub.loaded: + setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.installed })) handleInstall() break + case InstallStepFromGitHub.installed: + break } } const handleBack = () => { setState((prevState) => { switch (prevState.step) { - case InstallStepFromGitHub.setVersion: + case InstallStepFromGitHub.selectPackage: return { ...prevState, step: InstallStepFromGitHub.setUrl } - case InstallStepFromGitHub.setPackage: - return { ...prevState, step: InstallStepFromGitHub.setVersion } + case InstallStepFromGitHub.loaded: + return { ...prevState, step: InstallStepFromGitHub.selectPackage } default: return prevState } @@ -155,22 +138,24 @@ const InstallFromGitHub: React.FC = ({ onClose }) => { onCancel={onClose} /> )} - {state.step === InstallStepFromGitHub.setVersion && ( - setState(prevState => ({ ...prevState, selectedVersion: item.value as string }))} + onSelectVersion={item => setState(prevState => ({ ...prevState, selectedVersion: item.value as string }))} + selectedPackage={state.selectedPackage} + packages={packages} + onSelectPackage={item => setState(prevState => ({ ...prevState, selectedPackage: item.value as string }))} onNext={handleNext} onBack={handleBack} /> )} - {state.step === InstallStepFromGitHub.setPackage && ( - setState(prevState => ({ ...prevState, selectedPackage: item.value as string }))} - onInstall={handleInstall} + {state.step === InstallStepFromGitHub.loaded && ( + )} {state.step === InstallStepFromGitHub.installed && ( diff --git a/web/app/components/plugins/install-plugin/install-from-github/steps/loaded.tsx b/web/app/components/plugins/install-plugin/install-from-github/steps/loaded.tsx new file mode 100644 index 0000000000..42d847c584 --- /dev/null +++ b/web/app/components/plugins/install-plugin/install-from-github/steps/loaded.tsx @@ -0,0 +1,55 @@ +'use client' + +import React from 'react' +import Button from '@/app/components/base/button' +import type { PluginDeclaration } from '../../../types' +import Card from '../../../card' +import Badge, { BadgeState } from '@/app/components/base/badge/index' +import { pluginManifestToCardPluginProps } from '../../utils' +import { useTranslation } from 'react-i18next' + +type LoadedProps = { + isLoading: boolean + payload: PluginDeclaration + onBack: () => void + onInstall: () => void +} + +const i18nPrefix = 'plugin.installModal' + +const Loaded: React.FC = ({ isLoading, payload, onBack, onInstall }) => { + const { t } = useTranslation() + return ( + <> +
+

{t(`${i18nPrefix}.readyToInstall`)}

+
+
+ {payload.version}} + /> +
+
+ + +
+ + ) +} + +export default Loaded diff --git a/web/app/components/plugins/install-plugin/install-from-github/steps/setVersion.tsx b/web/app/components/plugins/install-plugin/install-from-github/steps/selectPackage.tsx similarity index 57% rename from web/app/components/plugins/install-plugin/install-from-github/steps/setVersion.tsx rename to web/app/components/plugins/install-plugin/install-from-github/steps/selectPackage.tsx index 042ab2b093..70ef1d8522 100644 --- a/web/app/components/plugins/install-plugin/install-from-github/steps/setVersion.tsx +++ b/web/app/components/plugins/install-plugin/install-from-github/steps/selectPackage.tsx @@ -1,18 +1,32 @@ +'use client' + import React from 'react' import type { Item } from '@/app/components/base/select' import { PortalSelect } from '@/app/components/base/select' import Button from '@/app/components/base/button' import { useTranslation } from 'react-i18next' -type SetVersionProps = { +type SelectPackageProps = { selectedVersion: string versions: Item[] - onSelect: (item: Item) => void + onSelectVersion: (item: Item) => void + selectedPackage: string + packages: Item[] + onSelectPackage: (item: Item) => void onNext: () => void onBack: () => void } -const SetVersion: React.FC = ({ selectedVersion, versions, onSelect, onNext, onBack }) => { +const SelectPackage: React.FC = ({ + selectedVersion, + versions, + onSelectVersion, + selectedPackage, + packages, + onSelectPackage, + onNext, + onBack, +}) => { const { t } = useTranslation() return ( <> @@ -24,11 +38,24 @@ const SetVersion: React.FC = ({ selectedVersion, versions, onSe + +
@@ -50,4 +77,4 @@ const SetVersion: React.FC = ({ selectedVersion, versions, onSe ) } -export default SetVersion +export default SelectPackage diff --git a/web/app/components/plugins/install-plugin/install-from-github/steps/setPackage.tsx b/web/app/components/plugins/install-plugin/install-from-github/steps/setPackage.tsx deleted file mode 100644 index 2db55aa56f..0000000000 --- a/web/app/components/plugins/install-plugin/install-from-github/steps/setPackage.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react' -import type { Item } from '@/app/components/base/select' -import { PortalSelect } from '@/app/components/base/select' -import Button from '@/app/components/base/button' -import { useTranslation } from 'react-i18next' - -type SetPackageProps = { - selectedPackage: string - packages: Item[] - onSelect: (item: Item) => void - onInstall: () => void - onBack: () => void -} - -const SetPackage: React.FC = ({ selectedPackage, packages, onSelect, onInstall, onBack }) => { - const { t } = useTranslation() - return ( - <> - - -
- - -
- - ) -} - -export default SetPackage diff --git a/web/app/components/plugins/install-plugin/install-from-github/steps/setURL.tsx b/web/app/components/plugins/install-plugin/install-from-github/steps/setURL.tsx index 9ec6cd6eee..c6ce006f37 100644 --- a/web/app/components/plugins/install-plugin/install-from-github/steps/setURL.tsx +++ b/web/app/components/plugins/install-plugin/install-from-github/steps/setURL.tsx @@ -1,3 +1,5 @@ +'use client' + import React from 'react' import Button from '@/app/components/base/button' import { useTranslation } from 'react-i18next' diff --git a/web/app/components/plugins/install-plugin/utils.ts b/web/app/components/plugins/install-plugin/utils.ts index 8b3e850deb..7677bf5b73 100644 --- a/web/app/components/plugins/install-plugin/utils.ts +++ b/web/app/components/plugins/install-plugin/utils.ts @@ -1,4 +1,5 @@ import type { Plugin, PluginDeclaration, PluginManifestInMarket } from '../types' +import type { GitHubUrlInfo } from '@/app/components/plugins/types' export const pluginManifestToCardPluginProps = (pluginManifest: PluginDeclaration): Plugin => { return { @@ -18,6 +19,7 @@ export const pluginManifestToCardPluginProps = (pluginManifest: PluginDeclaratio endpoint: { settings: [], }, + tags: [], } } @@ -39,5 +41,11 @@ export const pluginManifestInMarketToPluginProps = (pluginManifest: PluginManife endpoint: { settings: [], }, + tags: [], } } + +export const parseGitHubUrl = (url: string): GitHubUrlInfo => { + const match = url.match(/^https:\/\/github\.com\/([^/]+)\/([^/]+)\/?$/) + return match ? { isValid: true, owner: match[1], repo: match[2] } : { isValid: false } +} diff --git a/web/app/components/plugins/types.ts b/web/app/components/plugins/types.ts index ae16b0b300..f3cfd7f93c 100644 --- a/web/app/components/plugins/types.ts +++ b/web/app/components/plugins/types.ts @@ -133,8 +133,8 @@ export type Permissions = { export enum InstallStepFromGitHub { setUrl = 'url', - setVersion = 'version', - setPackage = 'package', + selectPackage = 'selecting', + loaded = 'loaded', installed = 'installed', } @@ -205,6 +205,11 @@ export type InstallPackageResponse = { task_id: string } +export type uploadGitHubResponse = { + plugin_unique_identifier: string + manifest: PluginDeclaration +} + export type DebugInfo = { key: string host: string diff --git a/web/service/plugins.ts b/web/service/plugins.ts index 43a57a24b4..e122b8efea 100644 --- a/web/service/plugins.ts +++ b/web/service/plugins.ts @@ -14,6 +14,7 @@ import type { TaskStatusResponse, UninstallPluginResponse, UpdateEndpointRequest, + uploadGitHubResponse, } from '@/app/components/plugins/types' import type { DebugInfo as DebugInfoTypes } from '@/app/components/plugins/types' import type { @@ -51,12 +52,6 @@ export const disableEndpoint: Fetcher(url, { body: { endpoint_id: endpointID } }) } -export const installPackageFromGitHub: Fetcher = ({ repo, version, package: packageName }) => { - return post('/workspaces/current/plugin/upload/github', { - body: { repo, version, package: packageName }, - }) -} - export const fetchDebugKey = async () => { return get('/workspaces/current/plugin/debugging-key') } @@ -76,6 +71,22 @@ export const installPackageFromLocal = async (uniqueIdentifier: string) => { }) } +export const uploadGitHub = async (repoUrl: string, selectedVersion: string, selectedPackage: string) => { + return post('/workspaces/current/plugin/upload/github', { + body: { + repo: repoUrl, + version: selectedVersion, + package: selectedPackage, + }, + }) +} + +export const installPackageFromGitHub = async (uniqueIdentifier: string) => { + return post('/workspaces/current/plugin/install/github', { + body: { plugin_unique_identifiers: [uniqueIdentifier] }, + }) +} + export const fetchIcon = (tenantId: string, fileName: string) => { return get(`workspaces/current/plugin/icon?tenant_id=${tenantId}&filename=${fileName}`) } From ca50522f80c90a9ce988796d7a64b6674e872f7b Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 1 Nov 2024 15:21:30 +0800 Subject: [PATCH 005/195] feat: set tool params --- .../app/configuration/config/agent/agent-tools/index.tsx | 2 +- .../workflow/block-selector/tool/action-item.tsx | 9 +++++++-- web/app/components/workflow/block-selector/tool/tool.tsx | 7 +++++++ web/app/components/workflow/block-selector/types.ts | 1 + 4 files changed, 16 insertions(+), 3 deletions(-) diff --git a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx index bbc352b9b4..57d2e5f632 100644 --- a/web/app/components/app/configuration/config/agent/agent-tools/index.tsx +++ b/web/app/components/app/configuration/config/agent/agent-tools/index.tsx @@ -91,7 +91,7 @@ const AgentTools: FC = () => { provider_name: tool.provider_name, tool_name: tool.tool_name, tool_label: tool.tool_label, - tool_parameters: {}, + tool_parameters: tool.params, enabled: true, }) }) diff --git a/web/app/components/workflow/block-selector/tool/action-item.tsx b/web/app/components/workflow/block-selector/tool/action-item.tsx index c233200b05..e33f625861 100644 --- a/web/app/components/workflow/block-selector/tool/action-item.tsx +++ b/web/app/components/workflow/block-selector/tool/action-item.tsx @@ -10,14 +10,12 @@ import { useGetLanguage } from '@/context/i18n' import BlockIcon from '../../block-icon' type Props = { - className?: string provider: ToolWithProvider payload: Tool onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void } const ToolItem: FC = ({ - className, provider, payload, onSelect, @@ -46,6 +44,12 @@ const ToolItem: FC = ({ key={payload.name} className='rounded-lg pl-[21px] hover:bg-state-base-hover cursor-pointer' onClick={() => { + const params: Record = {} + if (payload.parameters) { + payload.parameters.forEach((item) => { + params[item.name] = '' + }) + } onSelect(BlockEnum.Tool, { provider_id: provider.id, provider_type: provider.type, @@ -53,6 +57,7 @@ const ToolItem: FC = ({ tool_name: payload.name, tool_label: payload.label[language], title: payload.label[language], + params, }) }} > diff --git a/web/app/components/workflow/block-selector/tool/tool.tsx b/web/app/components/workflow/block-selector/tool/tool.tsx index 679f0b0e2e..f7433b8e60 100644 --- a/web/app/components/workflow/block-selector/tool/tool.tsx +++ b/web/app/components/workflow/block-selector/tool/tool.tsx @@ -66,6 +66,12 @@ const Tool: FC = ({ toggleFold() return } + // TODO: get workflow and custom tool params + // if (payload.parameters) { + // payload.parameters.forEach((item) => { + // params[item.name] = '' + // }) + // } onSelect(BlockEnum.Tool, { provider_id: payload.id, provider_type: payload.type, @@ -73,6 +79,7 @@ const Tool: FC = ({ tool_name: payload.name, tool_label: payload.label[language], title: payload.label[language], + params: {}, }) }} > diff --git a/web/app/components/workflow/block-selector/types.ts b/web/app/components/workflow/block-selector/types.ts index affa2488b9..9bdbf5cb3c 100644 --- a/web/app/components/workflow/block-selector/types.ts +++ b/web/app/components/workflow/block-selector/types.ts @@ -25,4 +25,5 @@ export type ToolDefaultValue = { tool_name: string tool_label: string title: string + params: Record } From 5d5db7c6c19f54cfa94e67c4e9a986771286e773 Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 1 Nov 2024 15:28:10 +0800 Subject: [PATCH 006/195] fix: key too long breaks ui --- web/app/components/plugins/base/key-value-item.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/plugins/base/key-value-item.tsx b/web/app/components/plugins/base/key-value-item.tsx index 50d3b53535..faa81f64e7 100644 --- a/web/app/components/plugins/base/key-value-item.tsx +++ b/web/app/components/plugins/base/key-value-item.tsx @@ -46,7 +46,7 @@ const KeyValueItem: FC = ({
{label}
- + {value} From 4caa8f38bcb8a197589f2d2fb54badd5854df62f Mon Sep 17 00:00:00 2001 From: JzoNg Date: Fri, 1 Nov 2024 15:33:15 +0800 Subject: [PATCH 007/195] hide plugin detail --- .../plugins/plugin-page/plugins-panel.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/web/app/components/plugins/plugin-page/plugins-panel.tsx b/web/app/components/plugins/plugin-page/plugins-panel.tsx index 8dbbf8eaa5..768af9d706 100644 --- a/web/app/components/plugins/plugin-page/plugins-panel.tsx +++ b/web/app/components/plugins/plugin-page/plugins-panel.tsx @@ -44,14 +44,16 @@ const PluginsPanel = () => {
- { - setCurrentPluginDetail(undefined) - setCurrentEndpoints([]) - }} - /> + {false && ( + { + setCurrentPluginDetail(undefined) + setCurrentEndpoints([]) + }} + /> + )} ) } From 824ed7d6c2a493695d1ef602d41819f3448b07c0 Mon Sep 17 00:00:00 2001 From: Yi Date: Fri, 1 Nov 2024 15:39:55 +0800 Subject: [PATCH 008/195] chore: plugin button --- web/app/components/header/plugins-nav/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/app/components/header/plugins-nav/index.tsx b/web/app/components/header/plugins-nav/index.tsx index c2ad9398f4..7cef0ce6fb 100644 --- a/web/app/components/header/plugins-nav/index.tsx +++ b/web/app/components/header/plugins-nav/index.tsx @@ -17,7 +17,7 @@ const PluginsNav = ({ -
+
From 8874837dc3119df0653195cdd2fff551a10fa2d6 Mon Sep 17 00:00:00 2001 From: StyleZhang Date: Fri, 1 Nov 2024 16:08:05 +0800 Subject: [PATCH 009/195] feat: plugin tasks --- .../steps/install.tsx | 3 + .../components/plugins/plugin-page/hooks.ts | 37 -------- .../components/plugins/plugin-page/index.tsx | 27 ++---- .../plugins/plugin-page/install-info.tsx | 86 +++++++++++++++++++ .../plugin-page/install-plugin-dropdown.tsx | 2 +- .../components/plugins/plugin-page/store.tsx | 40 +++++++++ 6 files changed, 138 insertions(+), 57 deletions(-) delete mode 100644 web/app/components/plugins/plugin-page/hooks.ts create mode 100644 web/app/components/plugins/plugin-page/install-info.tsx create mode 100644 web/app/components/plugins/plugin-page/store.tsx 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 index 8461eeb425..da5357d87d 100644 --- 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 @@ -10,6 +10,7 @@ import { RiLoader2Line } from '@remixicon/react' import Badge, { BadgeState } from '@/app/components/base/badge/index' import { installPackageFromLocal } from '@/service/plugins' import checkTaskStatus from '../../base/check-task-status' +import { usePluginTasksStore } from '@/app/components/plugins/plugin-page/store' const i18nPrefix = 'plugin.installModal' @@ -42,6 +43,7 @@ const Installed: FC = ({ onCancel() } + const setPluginTasksWithPolling = usePluginTasksStore(s => s.setPluginTasksWithPolling) const handleInstall = async () => { if (isInstalling) return setIsInstalling(true) @@ -56,6 +58,7 @@ const Installed: FC = ({ onInstalled() return } + setPluginTasksWithPolling() await check({ taskId, pluginUniqueIdentifier: uniqueIdentifier, diff --git a/web/app/components/plugins/plugin-page/hooks.ts b/web/app/components/plugins/plugin-page/hooks.ts deleted file mode 100644 index 395d90164a..0000000000 --- a/web/app/components/plugins/plugin-page/hooks.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { - useCallback, - useEffect, - useState, -} from 'react' -import { useRequest } from 'ahooks' -import type { PluginTask } from '../types' -import { fetchPluginTasks } from '@/service/plugins' - -export const usePluginTasks = () => { - const [pluginTasks, setPluginTasks] = useState([]) - - const handleUpdatePluginTasks = async (callback: (tasks: PluginTask[]) => void) => { - const { tasks } = await fetchPluginTasks() - setPluginTasks(tasks) - callback(tasks) - } - - const { run, cancel } = useRequest(handleUpdatePluginTasks, { - manual: true, - pollingInterval: 3000, - pollingErrorRetryCount: 2, - }) - - const checkHasPluginTasks = useCallback((tasks: PluginTask[]) => { - if (!tasks.length) - cancel() - }, [cancel]) - - useEffect(() => { - run(checkHasPluginTasks) - }, [run, checkHasPluginTasks]) - - return { - pluginTasks, - } -} diff --git a/web/app/components/plugins/plugin-page/index.tsx b/web/app/components/plugins/plugin-page/index.tsx index d8b7d649d8..c5f8d382b3 100644 --- a/web/app/components/plugins/plugin-page/index.tsx +++ b/web/app/components/plugins/plugin-page/index.tsx @@ -5,7 +5,6 @@ import { useTranslation } from 'react-i18next' import { RiDragDropLine, RiEqualizer2Line, - RiInstallFill, } from '@remixicon/react' import { useBoolean } from 'ahooks' import InstallFromLocalPackage from '../install-plugin/install-from-local-package' @@ -17,7 +16,8 @@ import InstallPluginDropdown from './install-plugin-dropdown' import { useUploader } from './use-uploader' import usePermission from './use-permission' import DebugInfo from './debug-info' -import { usePluginTasks } from './hooks' +import { usePluginTasksStore } from './store' +import InstallInfo from './install-info' import { useTabSearchParams } from '@/hooks/use-tab-searchparams' import Button from '@/app/components/base/button' import TabSlider from '@/app/components/base/tab-slider' @@ -125,7 +125,11 @@ const PluginPage = ({ const { dragging, fileUploader, fileChangeHandle, removeFile } = uploaderProps - const { pluginTasks } = usePluginTasks() + const setPluginTasksWithPolling = usePluginTasksStore(s => s.setPluginTasksWithPolling) + + useEffect(() => { + setPluginTasksWithPolling() + }, [setPluginTasksWithPolling]) return (
-
- -
+ {canManagement && ( setActiveTab('discover')} diff --git a/web/app/components/plugins/plugin-page/install-info.tsx b/web/app/components/plugins/plugin-page/install-info.tsx new file mode 100644 index 0000000000..4d3b076883 --- /dev/null +++ b/web/app/components/plugins/plugin-page/install-info.tsx @@ -0,0 +1,86 @@ +import { + useState, +} from 'react' +import { + RiCheckboxCircleFill, + RiErrorWarningFill, + RiInstallLine, +} from '@remixicon/react' +import { + PortalToFollowElem, + PortalToFollowElemContent, + PortalToFollowElemTrigger, +} from '@/app/components/base/portal-to-follow-elem' +import Tooltip from '@/app/components/base/tooltip' +import Button from '@/app/components/base/button' +// import ProgressCircle from '@/app/components/base/progress-bar/progress-circle' +import { useMemo } from 'react' +import cn from '@/utils/classnames' + +const InstallInfo = () => { + const [open, setOpen] = useState(false) + const status = 'error' + const statusError = useMemo(() => status === 'error', [status]) + + return ( +
+ + setOpen(v => !v)}> + +
+ +
+ {/* */} + +
+
+
+
+ +
+
3 plugins failed to install
+
+
+ +
+
+ DuckDuckGo Search +
+ +
+
+
+
+
+ ) +} + +export default InstallInfo 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 605a9f36f0..5b8d4caa2e 100644 --- a/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx +++ b/web/app/components/plugins/plugin-page/install-plugin-dropdown.tsx @@ -119,7 +119,7 @@ const InstallPluginDropdown = ({ && ( setSelectedAction(null)} - onSuccess={() => { }} + onSuccess={() => {}} /> ) } diff --git a/web/app/components/plugins/plugin-page/store.tsx b/web/app/components/plugins/plugin-page/store.tsx new file mode 100644 index 0000000000..25074b973f --- /dev/null +++ b/web/app/components/plugins/plugin-page/store.tsx @@ -0,0 +1,40 @@ +import { create } from 'zustand' +import type { PluginTask } from '../types' +import { fetchPluginTasks } from '@/service/plugins' + +type PluginTasksStore = { + pluginTasks: PluginTask[] + setPluginTasks: (tasks: PluginTask[]) => void + setPluginTasksWithPolling: () => void +} + +let pluginTasksTimer: NodeJS.Timeout | null = null + +export const usePluginTasksStore = create(set => ({ + pluginTasks: [], + setPluginTasks: (tasks: PluginTask[]) => set({ pluginTasks: tasks }), + setPluginTasksWithPolling: async () => { + if (pluginTasksTimer) { + clearTimeout(pluginTasksTimer) + pluginTasksTimer = null + } + const handleUpdatePluginTasks = async () => { + const { tasks } = await fetchPluginTasks() + set({ pluginTasks: tasks }) + + if (tasks.length && !tasks.every(task => task.status === 'success')) { + pluginTasksTimer = setTimeout(() => { + handleUpdatePluginTasks() + }, 5000) + } + else { + if (pluginTasksTimer) { + clearTimeout(pluginTasksTimer) + pluginTasksTimer = null + } + } + } + + handleUpdatePluginTasks() + }, +})) From 3c8548c5621b55dc551cb022aa9e63d5a2deb792 Mon Sep 17 00:00:00 2001 From: Joel Date: Fri, 1 Nov 2024 16:12:09 +0800 Subject: [PATCH 010/195] feat: create tool model --- .../edit-custom-collection-modal/modal.tsx | 361 ++++++++++++++++++ .../workflow/block-selector/all-tools.tsx | 8 +- .../workflow/block-selector/tool-picker.tsx | 41 ++ 3 files changed, 409 insertions(+), 1 deletion(-) create mode 100644 web/app/components/tools/edit-custom-collection-modal/modal.tsx diff --git a/web/app/components/tools/edit-custom-collection-modal/modal.tsx b/web/app/components/tools/edit-custom-collection-modal/modal.tsx new file mode 100644 index 0000000000..099012b277 --- /dev/null +++ b/web/app/components/tools/edit-custom-collection-modal/modal.tsx @@ -0,0 +1,361 @@ +'use client' +import type { FC } from 'react' +import React, { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import { useDebounce, useGetState } from 'ahooks' +import produce from 'immer' +import { LinkExternal02, Settings01 } from '../../base/icons/src/vender/line/general' +import type { Credential, CustomCollectionBackend, CustomParamSchema, Emoji } from '../types' +import { AuthHeaderPrefix, AuthType } from '../types' +import GetSchema from './get-schema' +import ConfigCredentials from './config-credentials' +import TestApi from './test-api' +import cn from '@/utils/classnames' +import Input from '@/app/components/base/input' +import Textarea from '@/app/components/base/textarea' +import EmojiPicker from '@/app/components/base/emoji-picker' +import AppIcon from '@/app/components/base/app-icon' +import { parseParamsSchema } from '@/service/tools' +import LabelSelector from '@/app/components/tools/labels/selector' +import Toast from '@/app/components/base/toast' +import Modal from '../../base/modal' +import Button from '@/app/components/base/button' + +const fieldNameClassNames = 'py-2 leading-5 text-sm font-medium text-gray-900' +type Props = { + positionLeft?: boolean + payload: any + onHide: () => void + onAdd?: (payload: CustomCollectionBackend) => void + onRemove?: () => void + onEdit?: (payload: CustomCollectionBackend) => void +} +// Add and Edit +const EditCustomCollectionModal: FC = ({ + payload, + onHide, + onAdd, + onEdit, + onRemove, +}) => { + const { t } = useTranslation() + const isAdd = !payload + const isEdit = !!payload + + const [editFirst, setEditFirst] = useState(!isAdd) + const [paramsSchemas, setParamsSchemas] = useState(payload?.tools || []) + const [customCollection, setCustomCollection, getCustomCollection] = useGetState(isAdd + ? { + provider: '', + credentials: { + auth_type: AuthType.none, + api_key_header: 'Authorization', + api_key_header_prefix: AuthHeaderPrefix.basic, + }, + icon: { + content: '🕵️', + background: '#FEF7C3', + }, + schema_type: '', + schema: '', + } + : payload) + + const originalProvider = isEdit ? payload.provider : '' + + const [showEmojiPicker, setShowEmojiPicker] = useState(false) + const emoji = customCollection.icon + const setEmoji = (emoji: Emoji) => { + const newCollection = produce(customCollection, (draft) => { + draft.icon = emoji + }) + setCustomCollection(newCollection) + } + const schema = customCollection.schema + const debouncedSchema = useDebounce(schema, { wait: 500 }) + const setSchema = (schema: any) => { + const newCollection = produce(customCollection, (draft) => { + draft.schema = schema + }) + setCustomCollection(newCollection) + } + + useEffect(() => { + if (!debouncedSchema) + return + if (isEdit && editFirst) { + setEditFirst(false) + return + } + (async () => { + try { + const { parameters_schema, schema_type } = await parseParamsSchema(debouncedSchema) + const customCollection = getCustomCollection() + const newCollection = produce(customCollection, (draft) => { + draft.schema_type = schema_type + }) + setCustomCollection(newCollection) + setParamsSchemas(parameters_schema) + } + catch (e) { + const customCollection = getCustomCollection() + const newCollection = produce(customCollection, (draft) => { + draft.schema_type = '' + }) + setCustomCollection(newCollection) + setParamsSchemas([]) + } + })() + }, [debouncedSchema]) + + const [credentialsModalShow, setCredentialsModalShow] = useState(false) + const credential = customCollection.credentials + const setCredential = (credential: Credential) => { + const newCollection = produce(customCollection, (draft) => { + draft.credentials = credential + }) + setCustomCollection(newCollection) + } + + const [currTool, setCurrTool] = useState(null) + const [isShowTestApi, setIsShowTestApi] = useState(false) + + const [labels, setLabels] = useState(payload?.labels || []) + const handleLabelSelect = (value: string[]) => { + setLabels(value) + } + + const handleSave = () => { + // const postData = clone(customCollection) + const postData = produce(customCollection, (draft) => { + delete draft.tools + + if (draft.credentials.auth_type === AuthType.none) { + delete draft.credentials.api_key_header + delete draft.credentials.api_key_header_prefix + delete draft.credentials.api_key_value + } + + draft.labels = labels + }) + + let errorMessage = '' + if (!postData.provider) + errorMessage = t('common.errorMsg.fieldRequired', { field: t('tools.createTool.name') }) + + if (!postData.schema) + errorMessage = t('common.errorMsg.fieldRequired', { field: t('tools.createTool.schema') }) + + if (errorMessage) { + Toast.notify({ + type: 'error', + message: errorMessage, + }) + return + } + + if (isAdd) { + onAdd?.(postData) + return + } + + onEdit?.({ + ...postData, + original_provider: originalProvider, + }) + } + + const getPath = (url: string) => { + if (!url) + return '' + + try { + const path = decodeURI(new URL(url).pathname) + return path || '' + } + catch (e) { + return url + } + } + + return ( + <> + +
+
+ {t('tools.createTool.title')} +
+
+
+
{t('tools.createTool.name')} *
+
+ { setShowEmojiPicker(true) }} className='cursor-pointer' icon={emoji.content} background={emoji.background} /> + { + const newCollection = produce(customCollection, (draft) => { + draft.provider = e.target.value + }) + setCustomCollection(newCollection) + }} + /> +
+
+ + {/* Schema */} +
+
+
+
{t('tools.createTool.schema')}*
+
+ +
{t('tools.createTool.viewSchemaSpec')}
+ +
+
+ + +
+