feat: add plugin extension pages (iframe rendering, Page SDK, security hardening, i18n)

Co-Authored-By: Typer_Body <mcjiekejiemi@163.com>
This commit is contained in:
Junyan Qin
2026-04-24 17:19:45 +08:00
parent 195f6efeff
commit 12df9d6ee9
17 changed files with 572 additions and 95 deletions
@@ -31,6 +31,17 @@ export interface SidebarEntityItem {
// Install action types that can be triggered from sidebar
export type PluginInstallAction = 'local' | 'github' | null;
// Plugin page registered by a plugin
export interface PluginPageItem {
id: string; // "author/name/pageId"
name: string; // display label
pluginAuthor: string;
pluginName: string;
pageId: string;
path: string; // asset path (HTML file)
icon?: string; // optional icon name
}
// Entity lists and refresh functions exposed via context
export interface SidebarDataContextValue {
bots: SidebarEntityItem[];
@@ -38,6 +49,7 @@ export interface SidebarDataContextValue {
knowledgeBases: SidebarEntityItem[];
plugins: SidebarEntityItem[];
mcpServers: SidebarEntityItem[];
pluginPages: PluginPageItem[];
refreshBots: () => Promise<void>;
refreshPipelines: () => Promise<void>;
refreshKnowledgeBases: () => Promise<void>;
@@ -64,6 +76,7 @@ export function SidebarDataProvider({
const [knowledgeBases, setKnowledgeBases] = useState<SidebarEntityItem[]>([]);
const [plugins, setPlugins] = useState<SidebarEntityItem[]>([]);
const [mcpServers, setMCPServers] = useState<SidebarEntityItem[]>([]);
const [pluginPages, setPluginPages] = useState<PluginPageItem[]>([]);
const [detailEntityName, setDetailEntityName] = useState<string | null>(null);
const [pendingPluginInstallAction, setPendingPluginInstallAction] =
useState<PluginInstallAction>(null);
@@ -137,33 +150,67 @@ export function SidebarDataProvider({
}
}
setPlugins(
pluginsResp.plugins.map((plugin) => {
const meta = plugin.manifest.manifest.metadata;
const author = meta.author ?? '';
const name = meta.name;
const compositeKey = `${author}/${name}`;
const installedVersion = meta.version ?? '';
// Deduplicate plugins by composite key (prefer debug over installed)
const pluginMap = new Map<string, SidebarEntityItem>();
for (const plugin of pluginsResp.plugins) {
const meta = plugin.manifest.manifest.metadata;
const author = meta.author ?? '';
const name = meta.name;
const compositeKey = `${author}/${name}`;
const installedVersion = meta.version ?? '';
let hasUpdate = false;
if (plugin.install_source === 'marketplace') {
const latestVersion = marketplaceVersions.get(compositeKey);
if (latestVersion) {
hasUpdate = isNewerVersion(latestVersion, installedVersion);
let hasUpdate = false;
if (plugin.install_source === 'marketplace') {
const latestVersion = marketplaceVersions.get(compositeKey);
if (latestVersion) {
hasUpdate = isNewerVersion(latestVersion, installedVersion);
}
}
const item: SidebarEntityItem = {
id: compositeKey,
name: extractI18nObject(meta.label),
iconURL: httpClient.getPluginIconURL(author, name),
installSource: plugin.install_source,
installInfo: plugin.install_info,
hasUpdate,
debug: plugin.debug,
};
// If duplicate, prefer debug version
if (!pluginMap.has(compositeKey) || plugin.debug) {
pluginMap.set(compositeKey, item);
}
}
setPlugins(Array.from(pluginMap.values()));
// Extract plugin pages from spec.pages (deduplicate by id)
const pages: PluginPageItem[] = [];
const seenPageIds = new Set<string>();
for (const plugin of pluginsResp.plugins) {
const meta = plugin.manifest.manifest.metadata;
const author = meta.author ?? '';
const name = meta.name;
const spec = plugin.manifest.manifest.spec;
if (spec?.pages && Array.isArray(spec.pages)) {
for (const page of spec.pages) {
const pageId = `${author}/${name}/${page.id}`;
if (page.id && page.path && !seenPageIds.has(pageId)) {
seenPageIds.add(pageId);
pages.push({
id: pageId,
name: page.label ? extractI18nObject(page.label) : page.id,
pluginAuthor: author,
pluginName: name,
pageId: page.id,
path: page.path,
icon: page.icon,
});
}
}
return {
id: compositeKey,
name: extractI18nObject(meta.label),
iconURL: httpClient.getPluginIconURL(author, name),
installSource: plugin.install_source,
installInfo: plugin.install_info,
hasUpdate,
debug: plugin.debug,
};
}),
);
}
}
setPluginPages(pages);
} catch (error) {
console.error('Failed to fetch plugins for sidebar:', error);
}
@@ -214,6 +261,7 @@ export function SidebarDataProvider({
knowledgeBases,
plugins,
mcpServers,
pluginPages,
refreshBots,
refreshPipelines,
refreshKnowledgeBases,