'use client'; import PluginInstalledComponent, { PluginInstalledComponentRef, } from '@/app/home/plugins/plugin-installed/PluginInstalledComponent'; import MarketPage from '@/app/home/plugins/plugin-market/PluginMarketComponent'; // import PluginSortDialog from '@/app/home/plugins/plugin-sort/PluginSortDialog'; import styles from './plugins.module.css'; import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'; import { Button } from '@/components/ui/button'; import { PlusIcon, ChevronDownIcon, UploadIcon, StoreIcon, Download, Power, } from 'lucide-react'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { useState, useRef, useCallback, useEffect } from 'react'; import { httpClient } from '@/app/infra/http/HttpClient'; import { toast } from 'sonner'; import { useTranslation } from 'react-i18next'; import { PluginV4 } from '@/app/infra/entities/plugin'; import { systemInfo } from '@/app/infra/http/HttpClient'; import { ApiRespPluginSystemStatus } from '@/app/infra/entities/api'; enum PluginInstallStatus { WAIT_INPUT = 'wait_input', ASK_CONFIRM = 'ask_confirm', INSTALLING = 'installing', ERROR = 'error', } export default function PluginConfigPage() { const { t } = useTranslation(); const [modalOpen, setModalOpen] = useState(false); // const [sortModalOpen, setSortModalOpen] = useState(false); const [activeTab, setActiveTab] = useState('installed'); const [installSource, setInstallSource] = useState('local'); const [installInfo, setInstallInfo] = useState>({}); // eslint-disable-line @typescript-eslint/no-explicit-any const [pluginInstallStatus, setPluginInstallStatus] = useState(PluginInstallStatus.WAIT_INPUT); const [installError, setInstallError] = useState(null); const [githubURL, setGithubURL] = useState(''); const [isDragOver, setIsDragOver] = useState(false); const [pluginSystemStatus, setPluginSystemStatus] = useState(null); const [statusLoading, setStatusLoading] = useState(true); const pluginInstalledRef = useRef(null); const fileInputRef = useRef(null); useEffect(() => { const fetchPluginSystemStatus = async () => { try { setStatusLoading(true); const status = await httpClient.getPluginSystemStatus(); setPluginSystemStatus(status); } catch (error) { console.error('Failed to fetch plugin system status:', error); toast.error(t('plugins.failedToGetStatus')); } finally { setStatusLoading(false); } }; fetchPluginSystemStatus(); }, [t]); function watchTask(taskId: number) { let alreadySuccess = false; console.log('taskId:', taskId); // 每秒拉取一次任务状态 const interval = setInterval(() => { httpClient.getAsyncTask(taskId).then((resp) => { console.log('task status:', resp); if (resp.runtime.done) { clearInterval(interval); if (resp.runtime.exception) { setInstallError(resp.runtime.exception); setPluginInstallStatus(PluginInstallStatus.ERROR); } else { // success if (!alreadySuccess) { toast.success(t('plugins.installSuccess')); alreadySuccess = true; } setGithubURL(''); setModalOpen(false); pluginInstalledRef.current?.refreshPluginList(); } } }); }, 1000); } function handleModalConfirm() { installPlugin(installSource, installInfo as Record); // eslint-disable-line @typescript-eslint/no-explicit-any } function installPlugin( installSource: string, installInfo: Record, // eslint-disable-line @typescript-eslint/no-explicit-any ) { setPluginInstallStatus(PluginInstallStatus.INSTALLING); if (installSource === 'github') { httpClient .installPluginFromGithub(installInfo.url) .then((resp) => { const taskId = resp.task_id; watchTask(taskId); }) .catch((err) => { console.log('error when install plugin:', err); setInstallError(err.message); setPluginInstallStatus(PluginInstallStatus.ERROR); }); } else if (installSource === 'local') { httpClient .installPluginFromLocal(installInfo.file) .then((resp) => { const taskId = resp.task_id; watchTask(taskId); }) .catch((err) => { console.log('error when install plugin:', err); setInstallError(err.message); setPluginInstallStatus(PluginInstallStatus.ERROR); }); } else if (installSource === 'marketplace') { httpClient .installPluginFromMarketplace( installInfo.plugin_author, installInfo.plugin_name, installInfo.plugin_version, ) .then((resp) => { const taskId = resp.task_id; watchTask(taskId); }); } } const validateFileType = (file: File): boolean => { const allowedExtensions = ['.lbpkg', '.zip']; const fileName = file.name.toLowerCase(); return allowedExtensions.some((ext) => fileName.endsWith(ext)); }; const uploadPluginFile = useCallback( async (file: File) => { if (!pluginSystemStatus?.is_enable || !pluginSystemStatus?.is_connected) { toast.error(t('plugins.pluginSystemNotReady')); return; } if (!validateFileType(file)) { toast.error(t('plugins.unsupportedFileType')); return; } setModalOpen(true); setPluginInstallStatus(PluginInstallStatus.INSTALLING); setInstallError(null); installPlugin('local', { file }); }, [t, pluginSystemStatus], ); const handleFileSelect = useCallback(() => { if (fileInputRef.current) { fileInputRef.current.click(); } }, []); const handleFileChange = useCallback( (event: React.ChangeEvent) => { const file = event.target.files?.[0]; if (file) { uploadPluginFile(file); } // 清空input值,以便可以重复选择同一个文件 event.target.value = ''; }, [uploadPluginFile], ); const isPluginSystemReady = pluginSystemStatus?.is_enable && pluginSystemStatus?.is_connected; const handleDragOver = useCallback( (event: React.DragEvent) => { event.preventDefault(); if (isPluginSystemReady) { setIsDragOver(true); } }, [isPluginSystemReady], ); const handleDragLeave = useCallback((event: React.DragEvent) => { event.preventDefault(); setIsDragOver(false); }, []); const handleDrop = useCallback( (event: React.DragEvent) => { event.preventDefault(); setIsDragOver(false); if (!isPluginSystemReady) { toast.error(t('plugins.pluginSystemNotReady')); return; } const files = Array.from(event.dataTransfer.files); if (files.length > 0) { uploadPluginFile(files[0]); } }, [uploadPluginFile, isPluginSystemReady, t], ); // 插件系统未启用的状态显示 const renderPluginDisabledState = () => (

{t('plugins.systemDisabled')}

{t('plugins.systemDisabledDesc')}

); // 插件系统连接异常的状态显示 const renderPluginConnectionErrorState = () => (

{t('plugins.connectionError')}

{t('plugins.connectionErrorDesc')}

); // 加载状态显示 const renderLoadingState = () => (

{t('plugins.loadingStatus')}

); // 根据状态返回不同的内容 if (statusLoading) { return renderLoadingState(); } if (!pluginSystemStatus?.is_enable) { return renderPluginDisabledState(); } if (!pluginSystemStatus?.is_connected) { return renderPluginConnectionErrorState(); } return (
{t('plugins.installed')} {systemInfo.enable_marketplace && ( {t('plugins.marketplace')} )}
{/* */} {t('plugins.uploadLocal')} {systemInfo.enable_marketplace && ( { setActiveTab('market'); }} > {t('plugins.marketplace')} )}
{ setInstallSource('marketplace'); setInstallInfo({ plugin_author: plugin.author, plugin_name: plugin.name, plugin_version: plugin.latest_version, }); setPluginInstallStatus(PluginInstallStatus.ASK_CONFIRM); setModalOpen(true); }} />
{t('plugins.installPlugin')} {pluginInstallStatus === PluginInstallStatus.WAIT_INPUT && (

{t('plugins.onlySupportGithub')}

setGithubURL(e.target.value)} className="mb-4" />
)} {pluginInstallStatus === PluginInstallStatus.ASK_CONFIRM && (

{t('plugins.askConfirm', { name: installInfo.plugin_name, version: installInfo.plugin_version, })}

)} {pluginInstallStatus === PluginInstallStatus.INSTALLING && (

{t('plugins.installing')}

)} {pluginInstallStatus === PluginInstallStatus.ERROR && (

{t('plugins.installFailed')}

{installError}

)} {(pluginInstallStatus === PluginInstallStatus.WAIT_INPUT || pluginInstallStatus === PluginInstallStatus.ASK_CONFIRM) && ( <> )} {pluginInstallStatus === PluginInstallStatus.ERROR && ( )}
{/* 拖拽提示覆盖层 */} {isDragOver && (

{t('plugins.dragToUpload')}

)} {/* { pluginInstalledRef.current?.refreshPluginList(); }} /> */}
); }