feat(plugins): add plugin new version detection (#1865)

* feat(plugins): 添加插件更新检测功能

* perf: card style

---------

Co-authored-by: Junyan Qin <rockchinq@gmail.com>
This commit is contained in:
sheetung
2025-12-18 12:17:25 +08:00
committed by GitHub
parent 82e2123fe7
commit 3ab9ffb7b7
8 changed files with 132 additions and 10 deletions

View File

@@ -13,6 +13,7 @@ export interface IPluginCardVO {
status: string;
components: PluginComponent[];
debug: boolean;
hasUpdate?: boolean;
}
export class PluginCardVO implements IPluginCardVO {
@@ -28,6 +29,7 @@ export class PluginCardVO implements IPluginCardVO {
install_info: Record<string, any>; // eslint-disable-line @typescript-eslint/no-explicit-any
status: string;
components: PluginComponent[];
hasUpdate?: boolean;
constructor(prop: IPluginCardVO) {
this.author = prop.author;
@@ -42,5 +44,6 @@ export class PluginCardVO implements IPluginCardVO {
this.debug = prop.debug;
this.install_source = prop.install_source;
this.install_info = prop.install_info;
this.hasUpdate = prop.hasUpdate;
}
}

View File

@@ -7,6 +7,8 @@ import PluginForm from '@/app/home/plugins/components/plugin-installed/plugin-fo
import PluginReadme from '@/app/home/plugins/components/plugin-installed/plugin-readme/PluginReadme';
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,
@@ -72,10 +74,68 @@ const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
getPluginList();
}
function getPluginList() {
httpClient.getPlugins().then((value) => {
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(
value.plugins.map((plugin) => {
installedPluginsResp.plugins.map((plugin) => {
return new PluginCardVO({
author: plugin.manifest.manifest.metadata.author ?? '',
label: extractI18nObject(plugin.manifest.manifest.metadata.label),
@@ -97,7 +157,7 @@ const PluginInstalledComponent = forwardRef<PluginInstalledComponentRef>(
});
}),
);
});
}
}
useImperativeHandle(ref, () => ({

View File

@@ -159,12 +159,17 @@ export default function PluginCardComponent({
}}
>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
className="bg-white dark:bg-[#1f1f22] hover:bg-gray-100 dark:hover:bg-[#2a2a2d]"
>
<Ellipsis className="w-4 h-4" />
</Button>
<div className="relative">
<Button
variant="ghost"
className="bg-white dark:bg-[#1f1f22] hover:bg-gray-100 dark:hover:bg-[#2a2a2d]"
>
<Ellipsis className="w-4 h-4" />
</Button>
{cardVO.hasUpdate && (
<div className="absolute -top-0.5 -right-0.5 w-2.5 h-2.5 bg-red-500 rounded-full border-2 border-white dark:border-[#1f1f22]"></div>
)}
</div>
</DropdownMenuTrigger>
<DropdownMenuContent>
{/**upgrade */}
@@ -179,6 +184,11 @@ export default function PluginCardComponent({
>
<ArrowUp className="w-4 h-4" />
<span>{t('plugins.update')}</span>
{cardVO.hasUpdate && (
<Badge className="ml-auto bg-red-500 hover:bg-red-500 text-white text-[0.6rem] px-1.5 py-0 h-4">
{t('plugins.new')}
</Badge>
)}
</DropdownMenuItem>
)}
{/**view source */}