refactor(block-h): ai sidebar tabs resolve via plugin-contributable registry

This commit is contained in:
Agent Zero
2026-08-23 13:53:23 +02:00
parent b7ad5294a5
commit 59fdb614e0
3 changed files with 60 additions and 2 deletions
+15 -1
View File
@@ -5,6 +5,7 @@ import { SuggestionList } from '@/components/ai/SuggestionSidebar';
import { ImprovementPanel } from '@/components/ai/ImprovementPanel';
import { createSession, fetchSessions } from '@/api/ai';
import { useUIStore } from '@/store/uiStore';
import { getContributedTabs } from './sidebarTabs';
import { useTranslation } from 'react-i18next';
import { useUsers, useGroups } from '@/api/hooks';
import { Avatar } from '@/components/ui/Avatar';
@@ -35,7 +36,7 @@ const chevronRightIcon = (
);
interface TabDef {
key: 'proactive' | 'chat' | 'notifications' | 'team' | 'chatroom';
key: string;
label: string;
icon: (cls: string) => React.ReactNode;
testId: string;
@@ -139,6 +140,13 @@ export function AISidebar() {
{ key: 'notifications', label: t('topbar.notifications'), icon: bellIcon, testId: 'ai-sidebar-tab-notifications' },
{ key: 'team', label: 'Team', icon: teamIcon, testId: 'ai-sidebar-tab-team' },
{ key: 'chatroom', label: 'Chat', icon: chatBubbleIcon, testId: 'ai-sidebar-tab-chatroom' },
// Plugin-contributed tabs (Block H / HC-G)
...getContributedTabs().map((ct) => ({
key: ct.key,
label: ct.label,
icon: ct.icon,
testId: ct.testId,
})),
];
const activeTab = tabs.find((tab) => tab.key === aiSidebarTab) || tabs[0];
@@ -209,6 +217,12 @@ export function AISidebar() {
if (aiSidebarTab === 'chatroom') {
return <ChatPanel />;
}
// Plugin-contributed tabs (Block H / HC-G)
const contributed = getContributedTabs().find((ct) => ct.key === aiSidebarTab);
if (contributed) {
const ContributedComponent = contributed.component;
return <ContributedComponent />;
}
// chat tab
if (loading) {
return (
@@ -0,0 +1,42 @@
/**
* AI sidebar tab registry.
*
* Core owns the base tabs (chat/proactive/notifications/team/chatroom);
* plugins contribute additional tabs via ``registerSidebarTab`` during their
* frontend activation lifecycle (Block H / HC-G). Contributed tabs render a
* generic container component provided by the plugin.
*/
import type { ComponentType } from 'react';
type IconFn = (className: string) => JSX.Element;
export interface ContributedTab {
key: string;
label: string;
icon: IconFn;
testId: string;
component: ComponentType;
}
const _tabs = new Map<string, ContributedTab>();
export function registerSidebarTab(tab: ContributedTab): void {
if (_tabs.has(tab.key)) {
// eslint-disable-next-line no-console
console.warn(`[sidebarTabs] tab '${tab.key}' re-registered — overwriting`);
}
_tabs.set(tab.key, tab);
}
export function unregisterSidebarTabs(owner: string): void {
for (const [key, tab] of _tabs.entries()) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
if ((tab.component as any).__sidebar_owner__ === owner) {
_tabs.delete(key);
}
}
}
export function getContributedTabs(): ContributedTab[] {
return Array.from(_tabs.values());
}