diff --git a/web/app/components/header/plugins-nav/downloading-icon.module.css b/web/app/components/header/plugins-nav/downloading-icon.module.css
new file mode 100644
index 0000000000..c11a9f2f2c
--- /dev/null
+++ b/web/app/components/header/plugins-nav/downloading-icon.module.css
@@ -0,0 +1,44 @@
+@keyframes realistic-blink {
+ 0% { fill: #37ff37; opacity: 0.4; }
+ 15% { fill: #37ff37; opacity: 0.9; }
+ 25% { fill: #37ff37; opacity: 0.3; }
+ 38% { fill: #ff4444; opacity: 0.8; }
+ 42% { fill: #ff4444; opacity: 0.3; }
+ 58% { fill: #37ff37; opacity: 0.9; }
+ 65% { fill: #37ff37; opacity: 0.4; }
+ 79% { fill: #ff4444; opacity: 0.8; }
+ 84% { fill: #ff4444; opacity: 0.3; }
+ 92% { fill: #37ff37; opacity: 0.8; }
+ 100% { fill: #37ff37; opacity: 0.4; }
+}
+
+@keyframes drop {
+ 0% {
+ transform: translateY(-4px);
+ opacity: 0;
+ }
+ 5% {
+ transform: translateY(-4px);
+ opacity: 1;
+ }
+ 65% {
+ transform: translateY(2px);
+ opacity: 1;
+ }
+ 80% {
+ transform: translateY(2px);
+ opacity: 0;
+ }
+ 100% {
+ transform: translateY(2px);
+ opacity: 0;
+ }
+}
+
+#downloadingIconLight {
+ animation: realistic-blink 3s infinite ease-in-out;
+}
+
+#downloadingIconArrow {
+ animation: drop 1.2s cubic-bezier(0.4, 0, 1, 1) infinite;
+}
\ No newline at end of file
diff --git a/web/app/components/header/plugins-nav/downloading-icon.tsx b/web/app/components/header/plugins-nav/downloading-icon.tsx
new file mode 100644
index 0000000000..d3fc486445
--- /dev/null
+++ b/web/app/components/header/plugins-nav/downloading-icon.tsx
@@ -0,0 +1,17 @@
+import s from './downloading-icon.module.css'
+
+const DownloadingIcon = () => {
+ return (
+
+ )
+}
+
+export default DownloadingIcon
diff --git a/web/app/components/header/plugins-nav/index.tsx b/web/app/components/header/plugins-nav/index.tsx
index aa57e652b2..b2c6eba1a6 100644
--- a/web/app/components/header/plugins-nav/index.tsx
+++ b/web/app/components/header/plugins-nav/index.tsx
@@ -5,6 +5,9 @@ import Link from 'next/link'
import classNames from '@/utils/classnames'
import { Group } from '@/app/components/base/icons/src/vender/other'
import { useSelectedLayoutSegment } from 'next/navigation'
+import DownloadingIcon from './downloading-icon'
+import { usePluginTaskStatus } from '@/app/components/plugins/plugin-page/plugin-tasks/hooks'
+import Indicator from '@/app/components/header/indicator'
type PluginsNavProps = {
className?: string
@@ -16,17 +19,43 @@ const PluginsNav = ({
const { t } = useTranslation()
const selectedSegment = useSelectedLayoutSegment()
const activated = selectedSegment === 'plugins'
+ const {
+ isInstalling,
+ isInstallingWithError,
+ isFailed,
+ } = usePluginTaskStatus()
return (
-
+
+ {
+ (isFailed || isInstallingWithError) && !activated && (
+
+ )
+ }
-
+ {
+ (!(isInstalling || isInstallingWithError) || activated) && (
+
+ )
+ }
+ {
+ (isInstalling || isInstallingWithError) && !activated && (
+
+ )
+ }
{t('common.menus.plugins')}
diff --git a/web/app/components/plugins/plugin-page/plugin-tasks/hooks.ts b/web/app/components/plugins/plugin-page/plugin-tasks/hooks.ts
index e15f9b963a..f32a812c13 100644
--- a/web/app/components/plugins/plugin-page/plugin-tasks/hooks.ts
+++ b/web/app/components/plugins/plugin-page/plugin-tasks/hooks.ts
@@ -64,14 +64,16 @@ export const usePluginTaskStatus = () => {
const timerRef = useRef
(null)
useEffect(() => {
- if (isSuccess && opacity > 0) {
+ if (isSuccess) {
if (timerRef.current) {
clearTimeout(timerRef.current)
timerRef.current = null
}
- timerRef.current = setTimeout(() => {
- setOpacity(v => v - 0.1)
- }, 200)
+ if (opacity > 0) {
+ timerRef.current = setTimeout(() => {
+ setOpacity(v => v - 0.1)
+ }, 200)
+ }
}
if (!isSuccess)