diff --git a/.github/workflows/style.yml b/.github/workflows/style.yml index aed7fe8de3..e8cd0a7fe4 100644 --- a/.github/workflows/style.yml +++ b/.github/workflows/style.yml @@ -82,6 +82,33 @@ jobs: if: steps.changed-files.outputs.any_changed == 'true' run: pnpm run lint + docker-compose-template: + name: Docker Compose Template + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Check changed files + id: changed-files + uses: tj-actions/changed-files@v45 + with: + files: | + docker/generate_docker_compose + docker/.env.example + docker/docker-compose-template.yaml + docker/docker-compose.yaml + + - name: Generate Docker Compose + if: steps.changed-files.outputs.any_changed == 'true' + run: | + cd docker + ./generate_docker_compose + + - name: Check for changes + if: steps.changed-files.outputs.any_changed == 'true' + run: git diff --exit-code superlinter: name: SuperLinter diff --git a/docker/.env.example b/docker/.env.example index f3866a05e9..f85a3d94ee 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -513,7 +513,7 @@ TENCENT_VECTOR_DB_SHARD=1 TENCENT_VECTOR_DB_REPLICAS=2 # ElasticSearch configuration, only available when VECTOR_STORE is `elasticsearch` -ELASTICSEARCH_HOST=elasticsearch +ELASTICSEARCH_HOST=0.0.0.0 ELASTICSEARCH_PORT=9200 ELASTICSEARCH_USERNAME=elastic ELASTICSEARCH_PASSWORD=elastic diff --git a/docker/docker-compose-template.yaml b/docker/docker-compose-template.yaml index b1885edf33..0e85452f21 100644 --- a/docker/docker-compose-template.yaml +++ b/docker/docker-compose-template.yaml @@ -451,7 +451,7 @@ services: milvus-standalone: container_name: milvus-standalone - image: milvusdb/milvus:v2.3.1 + image: milvusdb/milvus:v2.5.0-beta profiles: - milvus command: [ 'milvus', 'run', 'standalone' ] @@ -535,20 +535,28 @@ services: container_name: elasticsearch profiles: - elasticsearch + - elasticsearch-ja restart: always volumes: + - ./elasticsearch/docker-entrypoint.sh:/docker-entrypoint-mount.sh - dify_es01_data:/usr/share/elasticsearch/data environment: ELASTIC_PASSWORD: ${ELASTICSEARCH_PASSWORD:-elastic} + VECTOR_STORE: ${VECTOR_STORE:-} cluster.name: dify-es-cluster node.name: dify-es0 discovery.type: single-node - xpack.license.self_generated.type: trial + xpack.license.self_generated.type: basic xpack.security.enabled: 'true' xpack.security.enrollment.enabled: 'false' xpack.security.http.ssl.enabled: 'false' ports: - ${ELASTICSEARCH_PORT:-9200}:9200 + deploy: + resources: + limits: + memory: 2g + entrypoint: [ 'sh', '-c', "sh /docker-entrypoint-mount.sh" ] healthcheck: test: [ 'CMD', 'curl', '-s', 'http://localhost:9200/_cluster/health?pretty' ] interval: 30s diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx index 8e3d8f9ec6..571b4fd3a6 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/config-popup.tsx @@ -11,6 +11,8 @@ import ProviderConfigModal from './provider-config-modal' import Indicator from '@/app/components/header/indicator' import Switch from '@/app/components/base/switch' import Tooltip from '@/app/components/base/tooltip' +import Divider from '@/app/components/base/divider' +import cn from '@/utils/classnames' const I18N_PREFIX = 'app.tracing' @@ -77,7 +79,6 @@ const ConfigPopup: FC = ({ className='ml-3' defaultValue={enabled} onChange={onStatusChange} - size='l' disabled={providerAllNotConfigured} /> ) @@ -106,15 +107,15 @@ const ConfigPopup: FC = ({ ) return ( -
+
-
{t(`${I18N_PREFIX}.tracing`)}
+
{t(`${I18N_PREFIX}.tracing`)}
-
+
{t(`${I18N_PREFIX}.${enabled ? 'enabled' : 'disabled'}`)}
{!readOnly && ( @@ -130,19 +131,18 @@ const ConfigPopup: FC = ({ : switchContent} )} -
-
+
{t(`${I18N_PREFIX}.tracingDescription`)}
-
-
+ +
{(providerAllConfigured || providerAllNotConfigured) ? ( <> -
{t(`${I18N_PREFIX}.configProviderTitle.${providerAllConfigured ? 'configured' : 'notConfigured'}`)}
+
{t(`${I18N_PREFIX}.configProviderTitle.${providerAllConfigured ? 'configured' : 'notConfigured'}`)}
{langSmithPanel} {langfusePanel} @@ -151,11 +151,11 @@ const ConfigPopup: FC = ({ ) : ( <> -
{t(`${I18N_PREFIX}.configProviderTitle.configured`)}
+
{t(`${I18N_PREFIX}.configProviderTitle.configured`)}
{langSmithConfig ? langSmithPanel : langfusePanel}
-
{t(`${I18N_PREFIX}.configProviderTitle.moreProvider`)}
+
{t(`${I18N_PREFIX}.configProviderTitle.moreProvider`)}
{!langSmithConfig ? langSmithPanel : langfusePanel}
diff --git a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/field.tsx b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/field.tsx index 87c84948be..84c48a6dde 100644 --- a/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/field.tsx +++ b/web/app/(commonLayout)/app/(appDetailLayout)/[appId]/overview/tracing/field.tsx @@ -26,7 +26,7 @@ const Field: FC = ({ return (
-
{label}
+
{label}
{isRequired && *}
= ({ ? ( -
-
+
+
-
{t(`${I18N_PREFIX}.title`)}{t(`app.tracing.${type}.title`)}
+
{t(`${I18N_PREFIX}.title`)}{t(`app.tracing.${type}.title`)}
@@ -230,16 +231,16 @@ const ProviderConfigModal: FC = ({ {isEdit && ( <> -
+ )}
-
-
- +
+
+ {t('common.modelProvider.encrypted.front')} = ({ }, [hasConfigured, isChosen, onChoose, readOnly]) return (
- {isChosen &&
{t(`${I18N_PREFIX}.inUse`)}
} + {isChosen &&
{t(`${I18N_PREFIX}.inUse`)}
}
{!readOnly && (
{hasConfigured && ( -
+
{t(`${I18N_PREFIX}.view`)}
)}
- +
{t(`${I18N_PREFIX}.config`)}
)} -
-
+
{t(`${I18N_PREFIX}.${type}.description`)}
diff --git a/web/app/components/app/overview/apikey-info-panel/index.tsx b/web/app/components/app/overview/apikey-info-panel/index.tsx index 661a88e823..2ca098a313 100644 --- a/web/app/components/app/overview/apikey-info-panel/index.tsx +++ b/web/app/components/app/overview/apikey-info-panel/index.tsx @@ -27,8 +27,8 @@ const APIKeyInfoPanel: FC = () => { return null return ( -
-
+
+
{isCloud && } {isCloud ? ( @@ -42,11 +42,11 @@ const APIKeyInfoPanel: FC = () => { )}
{isCloud && ( -
{t(`appOverview.apiKeyInfo.cloud.${'trial'}.description`)}
+
{t(`appOverview.apiKeyInfo.cloud.${'trial'}.description`)}
)}
) diff --git a/web/app/components/app/overview/apikey-info-panel/progress/index.tsx b/web/app/components/app/overview/apikey-info-panel/progress/index.tsx deleted file mode 100644 index cc8356e754..0000000000 --- a/web/app/components/app/overview/apikey-info-panel/progress/index.tsx +++ /dev/null @@ -1,29 +0,0 @@ -'use client' -import type { FC } from 'react' -import React from 'react' -import s from './style.module.css' -import cn from '@/utils/classnames' - -export type IProgressProps = { - className?: string - value: number // percent -} - -const Progress: FC = ({ - className, - value, -}) => { - const exhausted = value === 100 - return ( -
-
- {Array.from({ length: 10 }).fill(0).map((i, k) => ( -
- ))} -
- ) -} -export default React.memo(Progress) diff --git a/web/app/components/app/overview/apikey-info-panel/progress/style.module.css b/web/app/components/app/overview/apikey-info-panel/progress/style.module.css deleted file mode 100644 index 94c3ef45cf..0000000000 --- a/web/app/components/app/overview/apikey-info-panel/progress/style.module.css +++ /dev/null @@ -1,16 +0,0 @@ -.bar { - background: linear-gradient(90deg, rgba(41, 112, 255, 0.9) 0%, rgba(21, 94, 239, 0.9) 100%); -} - -.bar-error { - background: linear-gradient(90deg, rgba(240, 68, 56, 0.72) 0%, rgba(217, 45, 32, 0.9) 100%); -} - -.bar-item { - width: 10%; - border-right: 1px solid rgba(255, 255, 255, 0.5); -} - -.bar-item:last-of-type { - border-right: 0; -} \ No newline at end of file diff --git a/web/app/components/app/overview/appCard.tsx b/web/app/components/app/overview/appCard.tsx index f9f5c1fbff..99e847f8b6 100644 --- a/web/app/components/app/overview/appCard.tsx +++ b/web/app/components/app/overview/appCard.tsx @@ -1,6 +1,9 @@ 'use client' import type { HTMLProps } from 'react' import React, { useMemo, useState } from 'react' +import { + RiLoopLeftLine, +} from '@remixicon/react' import { Cog8ToothIcon, DocumentTextIcon, @@ -16,24 +19,25 @@ import style from './style.module.css' import type { ConfigParams } from './settings' import Tooltip from '@/app/components/base/tooltip' import AppBasic from '@/app/components/app-sidebar/basic' -import { asyncRunSafe, randomString } from '@/utils' +import { asyncRunSafe } from '@/utils' import Button from '@/app/components/base/button' import Tag from '@/app/components/base/tag' import Switch from '@/app/components/base/switch' import Divider from '@/app/components/base/divider' import CopyFeedback from '@/app/components/base/copy-feedback' +import ActionButton from '@/app/components/base/action-button' import Confirm from '@/app/components/base/confirm' import ShareQRCode from '@/app/components/base/qrcode' import SecretKeyButton from '@/app/components/develop/secret-key/secret-key-button' import type { AppDetailResponse } from '@/models/app' import { useAppContext } from '@/context/app-context' import type { AppSSO } from '@/types/app' +import cn from '@/utils/classnames' export type IAppCardProps = { className?: string appInfo: AppDetailResponse & Partial cardType?: 'api' | 'webapp' - customBgColor?: string onChangeStatus: (val: boolean) => Promise onSaveSiteConfig?: (params: ConfigParams) => Promise onGenerateCode?: () => Promise @@ -46,7 +50,6 @@ const EmbedIcon = ({ className = '' }: HTMLProps) => { function AppCard({ appInfo, cardType = 'webapp', - customBgColor, onChangeStatus, onSaveSiteConfig, onGenerateCode, @@ -92,10 +95,6 @@ function AppCard({ const appUrl = `${app_base_url}/${appMode}/${access_token}` const apiUrl = appInfo?.api_base_url - let bgColor = 'bg-primary-50 bg-opacity-40' - if (cardType === 'api') - bgColor = 'bg-purple-50' - const genClickFuncByName = (opName: string) => { switch (opName) { case t('appOverview.overview.appInfo.preview'): @@ -133,11 +132,8 @@ function AppCard({ } return ( -
-
+
+
-
+
{isApp ? t('appOverview.overview.appInfo.accessibleAddress') : t('appOverview.overview.apiInfo.accessibleAddress')}
-
+
-
+
{isApp ? appUrl : apiUrl}
- - {isApp && } - + + {isApp && } + {/* button copy link/ button regenerate */} {showConfirmDelete && ( -
setShowConfirmDelete(true)} - > -
-
+ setShowConfirmDelete(true)}> + + )}
- {!isApp && } + {!isApp && } {OPERATIONS_MAP[cardType].map((op) => { const disabled = op.opName === t('appOverview.overview.appInfo.settings.entry') diff --git a/web/app/components/base/copy-feedback/index.tsx b/web/app/components/base/copy-feedback/index.tsx index ead1eb1d18..bc1cca5205 100644 --- a/web/app/components/base/copy-feedback/index.tsx +++ b/web/app/components/base/copy-feedback/index.tsx @@ -1,10 +1,15 @@ 'use client' import React, { useState } from 'react' import { useTranslation } from 'react-i18next' +import { + RiClipboardFill, + RiClipboardLine, +} from '@remixicon/react' import { debounce } from 'lodash-es' import copy from 'copy-to-clipboard' import copyStyle from './style.module.css' import Tooltip from '@/app/components/base/tooltip' +import ActionButton from '@/app/components/base/action-button' type Props = { content: string @@ -13,7 +18,7 @@ type Props = { const prefixEmbedded = 'appOverview.overview.appInfo.embedded' -const CopyFeedback = ({ content, className }: Props) => { +const CopyFeedback = ({ content }: Props) => { const { t } = useTranslation() const [isCopied, setIsCopied] = useState(false) @@ -34,19 +39,15 @@ const CopyFeedback = ({ content, className }: Props) => { : t(`${prefixEmbedded}.copy`)) || '' } > -
+
-
+ > + {isCopied && } + {!isCopied && } +
+ ) } diff --git a/web/app/components/base/qrcode/index.tsx b/web/app/components/base/qrcode/index.tsx index 3f6ad3ce1d..08569a8c89 100644 --- a/web/app/components/base/qrcode/index.tsx +++ b/web/app/components/base/qrcode/index.tsx @@ -1,19 +1,20 @@ 'use client' import React, { useEffect, useRef, useState } from 'react' import { useTranslation } from 'react-i18next' +import { + RiQrCodeLine, +} from '@remixicon/react' import { QRCodeCanvas as QRCode } from 'qrcode.react' -import QrcodeStyle from './style.module.css' +import ActionButton from '@/app/components/base/action-button' import Tooltip from '@/app/components/base/tooltip' type Props = { content: string - selectorId: string - className?: string } const prefixEmbedded = 'appOverview.overview.appInfo.qrcode.title' -const ShareQRCode = ({ content, selectorId, className }: Props) => { +const ShareQRCode = ({ content }: Props) => { const { t } = useTranslation() const [isShow, setIsShow] = useState(false) const qrCodeRef = useRef(null) @@ -53,22 +54,21 @@ const ShareQRCode = ({ content, selectorId, className }: Props) => { -
-
+
+ + + {isShow && (
- -
-
{t('appOverview.overview.appInfo.qrcode.scan')}
-
·
-
{t('appOverview.overview.appInfo.qrcode.download')}
+ +
+
{t('appOverview.overview.appInfo.qrcode.scan')}
+
·
+
{t('appOverview.overview.appInfo.qrcode.download')}
)} diff --git a/web/app/components/base/qrcode/style.module.css b/web/app/components/base/qrcode/style.module.css deleted file mode 100644 index d84e5fa45c..0000000000 --- a/web/app/components/base/qrcode/style.module.css +++ /dev/null @@ -1,62 +0,0 @@ -.QrcodeIcon { - background-image: url(~@/app/components/develop/secret-key/assets/qrcode.svg); - background-position: center; - background-repeat: no-repeat; -} - -.QrcodeIcon:hover { - background-image: url(~@/app/components/develop/secret-key/assets/qrcode-hover.svg); - background-position: center; - background-repeat: no-repeat; -} - -.QrcodeIcon.show { - background-image: url(~@/app/components/develop/secret-key/assets/qrcode-hover.svg); - background-position: center; - background-repeat: no-repeat; -} - -.qrcodeimage { - position: relative; - object-fit: cover; -} -.scan { - margin: 0; - line-height: 1rem; - font-size: 0.75rem; -} -.download { - position: relative; - color: #155eef; - font-size: 0.75rem; - line-height: 1rem; -} -.text { - align-self: stretch; - display: flex; - flex-direction: row; - align-items: center; - justify-content: center; - white-space: nowrap; - gap: 4px; -} -.qrcodeform { - z-index: 50; - border: 0.5px solid #eaecf0; - display: flex; - flex-direction: column; - margin: 0 !important; - margin-top: 4px !important; - margin-left: -75px !important; - width: fit-content; - position: relative; - border-radius: 8px; - background-color: #fff; - box-shadow: 0 12px 16px -4px rgba(16, 24, 40, 0.08), - 0 4px 6px -2px rgba(16, 24, 40, 0.03); - overflow: hidden; - align-items: center; - justify-content: center; - padding: 15px; - gap: 8px; -} diff --git a/web/app/components/datasets/create/step-two/index.tsx b/web/app/components/datasets/create/step-two/index.tsx index 1627f8a193..dd05c664fa 100644 --- a/web/app/components/datasets/create/step-two/index.tsx +++ b/web/app/components/datasets/create/step-two/index.tsx @@ -393,7 +393,7 @@ const StepTwo = ({ score_threshold_enabled: false, score_threshold: 0.5, }) - // eslint-disable-next-line react-hooks/exhaustive-deps + // eslint-disable-next-line react-hooks/exhaustive-deps }, [rerankDefaultModel, isRerankDefaultModelValid]) const getCreationParams = () => { @@ -412,7 +412,6 @@ const StepTwo = ({ doc_form: currentDocForm, doc_language: docLanguage, process_rule: getProcessRule(), - retrieval_model: retrievalConfig, // Readonly. If want to changed, just go to settings page. embedding_model: embeddingModel.model, // Readonly embedding_model_provider: embeddingModel.provider, // Readonly @@ -424,7 +423,6 @@ const StepTwo = ({ if ( !isReRankModelSelected({ rerankModelList, - retrievalConfig, indexMethod: indexMethod as string, }) diff --git a/web/app/components/develop/secret-key/secret-key-button.tsx b/web/app/components/develop/secret-key/secret-key-button.tsx index dab319bab4..a9f2656392 100644 --- a/web/app/components/develop/secret-key/secret-key-button.tsx +++ b/web/app/components/develop/secret-key/secret-key-button.tsx @@ -23,7 +23,7 @@ const SecretKeyButton = ({ className, appId, iconCls, textCls }: ISecretKeyButto
-
{t('appApi.apiKey')}
+
{t('appApi.apiKey')}
setVisible(false)} appId={appId} /> diff --git a/web/app/components/header/plugins-nav/index.tsx b/web/app/components/header/plugins-nav/index.tsx index b2c6eba1a6..ab742c98cf 100644 --- a/web/app/components/header/plugins-nav/index.tsx +++ b/web/app/components/header/plugins-nav/index.tsx @@ -27,7 +27,7 @@ const PluginsNav = ({ return (
void) => { const foldIntoAnim = async () => { clearCountDown() const modalElem = document.querySelector(`.${modalClassName}`) as HTMLElement - const pluginTaskTriggerElem = document.getElementById('plugin-task-trigger') + const pluginTaskTriggerElem = document.getElementById('plugin-task-trigger') || document.querySelector('.plugins-nav-button') if (!modalElem || !pluginTaskTriggerElem) { onClose() diff --git a/web/app/components/plugins/install-plugin/hooks/use-hide-logic.ts b/web/app/components/plugins/install-plugin/hooks/use-hide-logic.ts new file mode 100644 index 0000000000..e5d2d1883a --- /dev/null +++ b/web/app/components/plugins/install-plugin/hooks/use-hide-logic.ts @@ -0,0 +1,40 @@ +import { useCallback, useState } from 'react' +import useFoldAnimInto from './use-fold-anim-into' + +const useHideLogic = (onClose: () => void) => { + const { + modalClassName, + foldIntoAnim: doFoldAnimInto, + clearCountDown, + countDownFoldIntoAnim, + } = useFoldAnimInto(onClose) + + const [isInstalling, doSetIsInstalling] = useState(false) + const setIsInstalling = useCallback((isInstalling: boolean) => { + if (!isInstalling) + clearCountDown() + doSetIsInstalling(isInstalling) + }, [clearCountDown]) + + const foldAnimInto = useCallback(() => { + if (isInstalling) { + doFoldAnimInto() + return + } + onClose() + }, [doFoldAnimInto, isInstalling, onClose]) + + const handleStartToInstall = useCallback(() => { + setIsInstalling(true) + countDownFoldIntoAnim() + }, [countDownFoldIntoAnim, setIsInstalling]) + + return { + modalClassName, + foldAnimInto, + setIsInstalling, + handleStartToInstall, + } +} + +export default useHideLogic 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 035de8b781..84750d65ad 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/index.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/index.tsx @@ -6,6 +6,8 @@ import { InstallStep } from '../../types' import type { Dependency } from '../../types' import ReadyToInstall from './ready-to-install' import { useTranslation } from 'react-i18next' +import useHideLogic from '../hooks/use-hide-logic' +import cn from '@/utils/classnames' const i18nPrefix = 'plugin.installModal' @@ -30,6 +32,13 @@ const InstallBundle: FC = ({ const { t } = useTranslation() const [step, setStep] = useState(installType === InstallType.fromMarketplace ? InstallStep.readyToInstall : InstallStep.uploading) + const { + modalClassName, + foldAnimInto, + setIsInstalling, + handleStartToInstall, + } = useHideLogic(onClose) + const getTitle = useCallback(() => { if (step === InstallStep.uploadFailed) return t(`${i18nPrefix}.uploadFailed`) @@ -42,8 +51,8 @@ const InstallBundle: FC = ({ return (
@@ -54,6 +63,8 @@ const InstallBundle: FC = ({ 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 index b534f0c6b9..63c0b5b07e 100644 --- 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 @@ -9,6 +9,8 @@ import type { Dependency, InstallStatusResponse, Plugin } from '../../types' type Props = { step: InstallStep onStepChange: (step: InstallStep) => void, + onStartToInstall: () => void + setIsInstalling: (isInstalling: boolean) => void allPlugins: Dependency[] onClose: () => void isFromMarketPlace?: boolean @@ -17,6 +19,8 @@ type Props = { const ReadyToInstall: FC = ({ step, onStepChange, + onStartToInstall, + setIsInstalling, allPlugins, onClose, isFromMarketPlace, @@ -27,13 +31,15 @@ const ReadyToInstall: FC = ({ setInstallStatus(installStatus) setInstalledPlugins(plugins) onStepChange(InstallStep.installed) - }, [onStepChange]) + setIsInstalling(false) + }, [onStepChange, setIsInstalling]) return ( <> {step === InstallStep.readyToInstall && ( diff --git a/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx b/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx index a1a17f2df1..c70e2759d0 100644 --- a/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx +++ b/web/app/components/plugins/install-plugin/install-bundle/steps/install.tsx @@ -12,6 +12,7 @@ const i18nPrefix = 'plugin.installModal' type Props = { allPlugins: Dependency[] + onStartToInstall?: () => void onInstalled: (plugins: Plugin[], installStatus: InstallStatusResponse[]) => void onCancel: () => void isFromMarketPlace?: boolean @@ -20,6 +21,7 @@ type Props = { const Install: FC = ({ allPlugins, + onStartToInstall, onInstalled, onCancel, isFromMarketPlace, @@ -65,6 +67,7 @@ const Install: FC = ({ }, }) const handleInstall = () => { + onStartToInstall?.() installOrUpdate({ payload: allPlugins.filter((_d, index) => selectedIndexes.includes(index)), plugin: selectedPlugins, 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 71f81f0ffa..7e43907564 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 @@ -16,6 +16,8 @@ import Loaded from './steps/loaded' import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon' import { useTranslation } from 'react-i18next' import useRefreshPluginList from '../hooks/use-refresh-plugin-list' +import cn from '@/utils/classnames' +import useHideLogic from '../hooks/use-hide-logic' const i18nPrefix = 'plugin.installFromGitHub' @@ -31,6 +33,13 @@ const InstallFromGitHub: React.FC = ({ updatePayload, on const { fetchReleases } = useGitHubReleases() const { refreshPluginList } = useRefreshPluginList() + const { + modalClassName, + foldAnimInto, + setIsInstalling, + handleStartToInstall, + } = useHideLogic(onClose) + const [state, setState] = useState({ step: updatePayload ? InstallStepFromGitHub.selectPackage : InstallStepFromGitHub.setUrl, repoUrl: updatePayload?.originalPackageInfo?.repo @@ -77,13 +86,28 @@ const InstallFromGitHub: React.FC = ({ updatePayload, on }) return } - await fetchReleases(owner, repo).then((fetchedReleases) => { - setState(prevState => ({ - ...prevState, - releases: fetchedReleases, - step: InstallStepFromGitHub.selectPackage, - })) - }) + try { + const fetchedReleases = await fetchReleases(owner, repo) + if (fetchedReleases.length > 0) { + setState(prevState => ({ + ...prevState, + releases: fetchedReleases, + step: InstallStepFromGitHub.selectPackage, + })) + } + else { + Toast.notify({ + type: 'error', + message: t('plugin.error.noReleasesFound'), + }) + } + } + catch (error) { + Toast.notify({ + type: 'error', + message: t('plugin.error.fetchReleasesError'), + }) + } } const handleError = (e: any, isInstall: boolean) => { @@ -115,14 +139,16 @@ const InstallFromGitHub: React.FC = ({ updatePayload, on const handleInstalled = useCallback(() => { setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.installed })) refreshPluginList(manifest) + setIsInstalling(false) onSuccess() - }, [manifest, onSuccess, refreshPluginList]) + }, [manifest, onSuccess, refreshPluginList, setIsInstalling]) const handleFailed = useCallback((errorMsg?: string) => { setState(prevState => ({ ...prevState, step: InstallStepFromGitHub.installFailed })) + setIsInstalling(false) if (errorMsg) setErrorMsg(errorMsg) - }, []) + }, [setIsInstalling]) const handleBack = () => { setState((prevState) => { @@ -140,9 +166,9 @@ const InstallFromGitHub: React.FC = ({ updatePayload, on return (
@@ -195,6 +221,7 @@ const InstallFromGitHub: React.FC = ({ updatePayload, on selectedVersion={state.selectedVersion} selectedPackage={state.selectedPackage} onBack={handleBack} + onStartToInstall={handleStartToInstall} onInstalled={handleInstalled} onFailed={handleFailed} /> 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 index e2de988a74..b1bcf01251 100644 --- 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 @@ -23,6 +23,7 @@ type LoadedProps = { selectedVersion: string selectedPackage: string onBack: () => void + onStartToInstall?: () => void onInstalled: () => void onFailed: (message?: string) => void } @@ -37,6 +38,7 @@ const Loaded: React.FC = ({ selectedVersion, selectedPackage, onBack, + onStartToInstall, onInstalled, onFailed, }) => { @@ -59,11 +61,13 @@ const Loaded: React.FC = ({ useEffect(() => { if (hasInstalled && uniqueIdentifier === installedInfoPayload.uniqueIdentifier) onInstalled() + // eslint-disable-next-line react-hooks/exhaustive-deps }, [hasInstalled]) const handleInstall = async () => { if (isInstalling) return setIsInstalling(true) + onStartToInstall?.() try { const { owner, repo } = parseGitHubUrl(repoUrl) 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 c126a38a1d..b37ab60079 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 @@ -9,6 +9,8 @@ import { useTranslation } from 'react-i18next' import useGetIcon from '@/app/components/plugins/install-plugin/base/use-get-icon' import ReadyToInstallPackage from './ready-to-install' import ReadyToInstallBundle from '../install-bundle/ready-to-install' +import useHideLogic from '../hooks/use-hide-logic' +import cn from '@/utils/classnames' const i18nPrefix = 'plugin.installModal' @@ -31,6 +33,13 @@ const InstallFromLocalPackage: React.FC = ({ const isBundle = file.name.endsWith('.difybndl') const [dependencies, setDependencies] = useState([]) + const { + modalClassName, + foldAnimInto, + setIsInstalling, + handleStartToInstall, + } = useHideLogic(onClose) + const getTitle = useCallback(() => { if (step === InstallStep.uploadFailed) return t(`${i18nPrefix}.uploadFailed`) @@ -76,8 +85,8 @@ const InstallFromLocalPackage: React.FC = ({ return (
@@ -99,6 +108,8 @@ const InstallFromLocalPackage: React.FC = ({ @@ -106,6 +117,8 @@ const InstallFromLocalPackage: React.FC = ({ void, + onStartToInstall: () => void + setIsInstalling: (isInstalling: boolean) => void onClose: () => void uniqueIdentifier: string | null, manifest: PluginDeclaration | null, @@ -20,6 +22,8 @@ type Props = { const ReadyToInstall: FC = ({ step, onStepChange, + onStartToInstall, + setIsInstalling, onClose, uniqueIdentifier, manifest, @@ -31,13 +35,15 @@ const ReadyToInstall: FC = ({ const handleInstalled = useCallback(() => { onStepChange(InstallStep.installed) refreshPluginList(manifest) - }, [manifest, onStepChange, refreshPluginList]) + setIsInstalling(false) + }, [manifest, onStepChange, refreshPluginList, setIsInstalling]) const handleFailed = useCallback((errorMsg?: string) => { onStepChange(InstallStep.installFailed) + setIsInstalling(false) if (errorMsg) onError(errorMsg) - }, [onError, onStepChange]) + }, [onError, onStepChange, setIsInstalling]) return ( <> @@ -49,6 +55,7 @@ const ReadyToInstall: FC = ({ onCancel={onClose} onInstalled={handleInstalled} onFailed={handleFailed} + onStartToInstall={onStartToInstall} /> ) } 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 72262894db..a5ce01d041 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 @@ -9,8 +9,8 @@ import Installed from '../base/installed' import { useTranslation } from 'react-i18next' import useRefreshPluginList from '../hooks/use-refresh-plugin-list' import ReadyToInstallBundle from '../install-bundle/ready-to-install' -import useFoldAnimInto from '../hooks/use-fold-anim-into' import cn from '@/utils/classnames' +import useHideLogic from '../hooks/use-hide-logic' const i18nPrefix = 'plugin.installModal' @@ -39,25 +39,10 @@ const InstallFromMarketplace: React.FC = ({ const { modalClassName, - foldIntoAnim: doFoldAnimInto, - clearCountDown, - countDownFoldIntoAnim, - } = useFoldAnimInto(onClose) - - const [isInstalling, setIsInstalling] = useState(false) - - const foldAnimInto = useCallback(() => { - if (isInstalling) { - doFoldAnimInto() - return - } - onClose() - }, [doFoldAnimInto, isInstalling, onClose]) - - const handleStartToInstall = useCallback(() => { - setIsInstalling(true) - countDownFoldIntoAnim() - }, [countDownFoldIntoAnim]) + foldAnimInto, + setIsInstalling, + handleStartToInstall, + } = useHideLogic(onClose) const getTitle = useCallback(() => { if (isBundle && step === InstallStep.installed) @@ -73,14 +58,14 @@ const InstallFromMarketplace: React.FC = ({ setStep(InstallStep.installed) refreshPluginList(manifest) setIsInstalling(false) - }, [manifest, refreshPluginList]) + }, [manifest, refreshPluginList, setIsInstalling]) const handleFailed = useCallback((errorMsg?: string) => { setStep(InstallStep.installFailed) setIsInstalling(false) if (errorMsg) setErrorMsg(errorMsg) - }, []) + }, [setIsInstalling]) return ( = ({
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 f177d1ac60..0c381c2a39 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 @@ -15,6 +15,7 @@ type Props = { list: Plugin[] searchText: string tags: string[] + toolContentClassName?: string disableMaxWidth?: boolean } @@ -23,6 +24,7 @@ const List = forwardRef<{ handleScroll: () => void }, Props>(({ searchText, tags, list, + toolContentClassName, disableMaxWidth = false, }, ref) => { const { t } = useTranslation() @@ -76,7 +78,7 @@ const List = forwardRef<{ handleScroll: () => void }, Props>(({ ) } - const maxWidthClassName = 'max-w-[300px]' + const maxWidthClassName = toolContentClassName || 'max-w-[300px]' return ( <> diff --git a/web/app/components/workflow/nodes/_base/components/install-plugin-button.tsx b/web/app/components/workflow/nodes/_base/components/install-plugin-button.tsx index bf4073358f..dc5930e15b 100644 --- a/web/app/components/workflow/nodes/_base/components/install-plugin-button.tsx +++ b/web/app/components/workflow/nodes/_base/components/install-plugin-button.tsx @@ -17,17 +17,19 @@ export const InstallPluginButton = (props: InstallPluginButtonProps) => { pluginIds: [uniqueIdentifier], enabled: !!uniqueIdentifier, }) - const install = useInstallPackageFromMarketPlace({ - onSuccess() { - manifest.refetch() - onSuccess?.() - }, - }) + const install = useInstallPackageFromMarketPlace() + const isLoading = manifest.isLoading || install.isPending + // await for refetch to get the new installed plugin, when manifest refetch, this component will unmount + || install.isSuccess const handleInstall: MouseEventHandler = (e) => { e.stopPropagation() - install.mutate(uniqueIdentifier) + install.mutate(uniqueIdentifier, { + onSuccess: async () => { + await manifest.refetch() + onSuccess?.() + }, + }) } - const isLoading = manifest.isLoading || install.isPending if (!manifest.data) return null if (manifest.data.plugins.some(plugin => plugin.id === uniqueIdentifier)) return null return