'use client'; import { useState, useEffect, forwardRef, useImperativeHandle } from 'react'; import { useRouter } from 'next/navigation'; import { PluginCardVO } from '@/app/home/plugins/components/plugin-installed/PluginCardVO'; import PluginCardComponent from '@/app/home/plugins/components/plugin-installed/plugin-card/PluginCardComponent'; import styles from '@/app/home/plugins/plugins.module.css'; import { httpClient } from '@/app/infra/http/HttpClient'; import { getCloudServiceClientSync } from '@/app/infra/http'; import { isNewerVersion } from '@/app/utils/versionCompare'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter, } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; import { useTranslation } from 'react-i18next'; import { extractI18nObject } from '@/i18n/I18nProvider'; import { toast } from 'sonner'; import { useAsyncTask, AsyncTaskStatus } from '@/hooks/useAsyncTask'; import { useSidebarData } from '@/app/home/components/home-sidebar/SidebarDataContext'; export interface PluginInstalledComponentRef { refreshPluginList: () => void; } enum PluginOperationType { DELETE = 'DELETE', UPDATE = 'UPDATE', } // eslint-disable-next-line react/display-name const PluginInstalledComponent = forwardRef( (props, ref) => { const { t } = useTranslation(); const router = useRouter(); const { refreshPlugins } = useSidebarData(); const [pluginList, setPluginList] = useState([]); const [showOperationModal, setShowOperationModal] = useState(false); const [operationType, setOperationType] = useState( PluginOperationType.DELETE, ); const [targetPlugin, setTargetPlugin] = useState(null); const [deleteData, setDeleteData] = useState(false); const asyncTask = useAsyncTask({ onSuccess: () => { const successMessage = operationType === PluginOperationType.DELETE ? t('plugins.deleteSuccess') : t('plugins.updateSuccess'); toast.success(successMessage); setShowOperationModal(false); getPluginList(); refreshPlugins(); }, onError: () => { // Error is already handled in the hook state }, }); useEffect(() => { initData(); }, []); function initData() { getPluginList(); } async function getPluginList() { try { // 获取已安装插件列表 const installedPluginsResp = await httpClient.getPlugins(); const installedPlugins = installedPluginsResp.plugins; // 获取市场插件列表 const client = getCloudServiceClientSync(); const marketplaceResp = await client.getMarketplacePlugins(1, 100); const marketplacePlugins = marketplaceResp.plugins; // 创建市场插件映射,便于快速查找 const marketplacePluginMap = new Map(); marketplacePlugins.forEach((plugin) => { const key = `${plugin.author}/${plugin.name}`; marketplacePluginMap.set(key, plugin); }); // 转换并比较版本号 const pluginCards = installedPlugins.map((plugin) => { const cardVO = new PluginCardVO({ author: plugin.manifest.manifest.metadata.author ?? '', label: extractI18nObject(plugin.manifest.manifest.metadata.label), description: extractI18nObject( plugin.manifest.manifest.metadata.description ?? { en_US: '', zh_Hans: '', }, ), debug: plugin.debug, enabled: plugin.enabled, name: plugin.manifest.manifest.metadata.name, version: plugin.manifest.manifest.metadata.version ?? '', status: plugin.status, components: plugin.components, priority: plugin.priority, install_source: plugin.install_source, install_info: plugin.install_info, }); // 检查是否来自市场且有更新 if (cardVO.install_source === 'marketplace') { const marketplaceKey = `${cardVO.author}/${cardVO.name}`; const marketplacePlugin = marketplacePluginMap.get(marketplaceKey); if (marketplacePlugin && marketplacePlugin.latest_version) { cardVO.hasUpdate = isNewerVersion( marketplacePlugin.latest_version, cardVO.version, ); } } return cardVO; }); setPluginList(pluginCards); } catch (error) { console.error('获取插件列表失败:', error); // 失败时仍显示已安装插件,不影响用户体验 const installedPluginsResp = await httpClient.getPlugins(); setPluginList( installedPluginsResp.plugins.map((plugin) => { return new PluginCardVO({ author: plugin.manifest.manifest.metadata.author ?? '', label: extractI18nObject(plugin.manifest.manifest.metadata.label), description: extractI18nObject( plugin.manifest.manifest.metadata.description ?? { en_US: '', zh_Hans: '', }, ), debug: plugin.debug, enabled: plugin.enabled, name: plugin.manifest.manifest.metadata.name, version: plugin.manifest.manifest.metadata.version ?? '', status: plugin.status, components: plugin.components, priority: plugin.priority, install_source: plugin.install_source, install_info: plugin.install_info, }); }), ); } } useImperativeHandle(ref, () => ({ refreshPluginList: getPluginList, })); function handlePluginClick(plugin: PluginCardVO) { const pluginId = `${plugin.author}/${plugin.name}`; router.push(`/home/plugins?id=${encodeURIComponent(pluginId)}`); } function handlePluginDelete(plugin: PluginCardVO) { setTargetPlugin(plugin); setOperationType(PluginOperationType.DELETE); setShowOperationModal(true); setDeleteData(false); asyncTask.reset(); } function handlePluginUpdate(plugin: PluginCardVO) { setTargetPlugin(plugin); setOperationType(PluginOperationType.UPDATE); setShowOperationModal(true); asyncTask.reset(); } function executeOperation() { if (!targetPlugin) return; const apiCall = operationType === PluginOperationType.DELETE ? httpClient.removePlugin( targetPlugin.author, targetPlugin.name, deleteData, ) : httpClient.upgradePlugin(targetPlugin.author, targetPlugin.name); apiCall .then((res) => { asyncTask.startTask(res.task_id); }) .catch((error) => { const errorMessage = operationType === PluginOperationType.DELETE ? t('plugins.deleteError') + error.message : t('plugins.updateError') + error.message; toast.error(errorMessage); }); } return ( <> { if (!open) { setShowOperationModal(false); setTargetPlugin(null); asyncTask.reset(); } }} > {operationType === PluginOperationType.DELETE ? t('plugins.deleteConfirm') : t('plugins.updateConfirm')} {asyncTask.status === AsyncTaskStatus.WAIT_INPUT && (
{operationType === PluginOperationType.DELETE ? t('plugins.confirmDeletePlugin', { author: targetPlugin?.author ?? '', name: targetPlugin?.name ?? '', }) : t('plugins.confirmUpdatePlugin', { author: targetPlugin?.author ?? '', name: targetPlugin?.name ?? '', })}
{operationType === PluginOperationType.DELETE && (
setDeleteData(checked === true) } />
)}
)} {asyncTask.status === AsyncTaskStatus.RUNNING && (
{operationType === PluginOperationType.DELETE ? t('plugins.deleting') : t('plugins.updating')}
)} {asyncTask.status === AsyncTaskStatus.ERROR && (
{operationType === PluginOperationType.DELETE ? t('plugins.deleteError') : t('plugins.updateError')}
{asyncTask.error}
)}
{asyncTask.status === AsyncTaskStatus.WAIT_INPUT && ( )} {asyncTask.status === AsyncTaskStatus.WAIT_INPUT && ( )} {asyncTask.status === AsyncTaskStatus.RUNNING && ( )} {asyncTask.status === AsyncTaskStatus.ERROR && ( )}
{pluginList.length === 0 ? (
{t('plugins.noPluginInstalled')}
) : (
{pluginList.map((vo, index) => { return (
handlePluginClick(vo)} onDeleteClick={() => handlePluginDelete(vo)} onUpgradeClick={() => handlePluginUpdate(vo)} />
); })}
)} ); }, ); export default PluginInstalledComponent;