refactor(block-h): ai sidebar tabs resolve via plugin-contributable registry
This commit is contained in:
@@ -5,6 +5,7 @@ import { SuggestionList } from '@/components/ai/SuggestionSidebar';
|
|||||||
import { ImprovementPanel } from '@/components/ai/ImprovementPanel';
|
import { ImprovementPanel } from '@/components/ai/ImprovementPanel';
|
||||||
import { createSession, fetchSessions } from '@/api/ai';
|
import { createSession, fetchSessions } from '@/api/ai';
|
||||||
import { useUIStore } from '@/store/uiStore';
|
import { useUIStore } from '@/store/uiStore';
|
||||||
|
import { getContributedTabs } from './sidebarTabs';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useUsers, useGroups } from '@/api/hooks';
|
import { useUsers, useGroups } from '@/api/hooks';
|
||||||
import { Avatar } from '@/components/ui/Avatar';
|
import { Avatar } from '@/components/ui/Avatar';
|
||||||
@@ -35,7 +36,7 @@ const chevronRightIcon = (
|
|||||||
);
|
);
|
||||||
|
|
||||||
interface TabDef {
|
interface TabDef {
|
||||||
key: 'proactive' | 'chat' | 'notifications' | 'team' | 'chatroom';
|
key: string;
|
||||||
label: string;
|
label: string;
|
||||||
icon: (cls: string) => React.ReactNode;
|
icon: (cls: string) => React.ReactNode;
|
||||||
testId: string;
|
testId: string;
|
||||||
@@ -139,6 +140,13 @@ export function AISidebar() {
|
|||||||
{ key: 'notifications', label: t('topbar.notifications'), icon: bellIcon, testId: 'ai-sidebar-tab-notifications' },
|
{ 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: 'team', label: 'Team', icon: teamIcon, testId: 'ai-sidebar-tab-team' },
|
||||||
{ key: 'chatroom', label: 'Chat', icon: chatBubbleIcon, testId: 'ai-sidebar-tab-chatroom' },
|
{ 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];
|
const activeTab = tabs.find((tab) => tab.key === aiSidebarTab) || tabs[0];
|
||||||
@@ -209,6 +217,12 @@ export function AISidebar() {
|
|||||||
if (aiSidebarTab === 'chatroom') {
|
if (aiSidebarTab === 'chatroom') {
|
||||||
return <ChatPanel />;
|
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
|
// chat tab
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
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());
|
||||||
|
}
|
||||||
@@ -2,7 +2,9 @@ import { create } from 'zustand';
|
|||||||
|
|
||||||
export type Theme = 'light' | 'dark' | 'system';
|
export type Theme = 'light' | 'dark' | 'system';
|
||||||
export type Locale = 'de' | 'en';
|
export type Locale = 'de' | 'en';
|
||||||
export type AISidebarTab = 'proactive' | 'chat' | 'notifications' | 'team' | 'chatroom';
|
// Plugin-contributed tabs add arbitrary keys (Block H / HC-G) — the union
|
||||||
|
// keeps autocomplete for the base tabs while allowing any string key.
|
||||||
|
export type AISidebarTab = 'proactive' | 'chat' | 'notifications' | 'team' | 'chatroom' | (string & {});
|
||||||
|
|
||||||
export interface Toast {
|
export interface Toast {
|
||||||
id: string;
|
id: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user