/**
* Quick Panel Entry Point
*
* This module provides the main controller for Quick Panel functionality.
* It orchestrates:
* - Shadow DOM host management
* - AI Chat panel lifecycle
* - Agent bridge communication
* - Keyboard shortcut handling (external)
*
* Usage in content script:
* ```typescript
* import { createQuickPanelController } from './quick-panel';
*
* const controller = createQuickPanelController();
*
* // Show panel (e.g., on keyboard shortcut)
* controller.show();
*
* // Hide panel
* controller.hide();
*
* // Toggle visibility
* controller.toggle();
*
* // Cleanup on unload
* controller.dispose();
* ```
*/
import { createAgentBridge, type QuickPanelAgentBridge } from './core/agent-bridge';
import {
mountQuickPanelShadowHost,
mountQuickPanelAiChatPanel,
type QuickPanelShadowHostManager,
type QuickPanelAiChatPanelManager,
} from './ui';
// ============================================================
// Types
// ============================================================
export interface QuickPanelControllerOptions {
/** Custom host element ID for Shadow DOM. Default: '__mcp_quick_panel_host__' */
hostId?: string;
/** Custom z-index for overlay. Default: 2147483647 (highest possible) */
zIndex?: number;
/** Panel title. Default: 'Agent' */
title?: string;
/** Panel subtitle. Default: 'Quick Panel' */
subtitle?: string;
/** Input placeholder. Default: 'Ask the agent...' */
placeholder?: string;
}
export interface QuickPanelController {
/** Show the Quick Panel (creates if not exists) */
show: () => void;
/** Hide the Quick Panel (disposes UI but keeps bridge alive) */
hide: () => void;
/** Toggle Quick Panel visibility */
toggle: () => void;
/** Check if panel is currently visible */
isVisible: () => boolean;
/** Fully dispose all resources */
dispose: () => void;
}
// ============================================================
// Constants
// ============================================================
const LOG_PREFIX = '[QuickPanelController]';
// ============================================================
// Main Factory
// ============================================================
/**
* Create a Quick Panel controller instance.
*
* The controller manages the full lifecycle of the Quick Panel UI,
* including Shadow DOM isolation, AI chat interface, and background
* communication.
*
* @example
* ```typescript
* // In content script
* const quickPanel = createQuickPanelController();
*
* // Listen for keyboard shortcut (e.g., Cmd+Shift+K)
* document.addEventListener('keydown', (e) => {
* if (e.metaKey && e.shiftKey && e.key === 'k') {
* e.preventDefault();
* quickPanel.toggle();
* }
* });
*
* // Cleanup on extension unload
* window.addEventListener('unload', () => {
* quickPanel.dispose();
* });
* ```
*/
export function createQuickPanelController(
options: QuickPanelControllerOptions = {},
): QuickPanelController {
let disposed = false;
// Shared agent bridge (persists across show/hide cycles)
let agentBridge: QuickPanelAgentBridge | null = null;
// UI components (created on show, disposed on hide)
let shadowHost: QuickPanelShadowHostManager | null = null;
let chatPanel: QuickPanelAiChatPanelManager | null = null;
/**
* Ensure agent bridge is initialized
*/
function ensureBridge(): QuickPanelAgentBridge {
if (!agentBridge || agentBridge.isDisposed()) {
agentBridge = createAgentBridge();
}
return agentBridge;
}
/**
* Dispose current UI (keeps bridge alive for potential reuse)
*/
function disposeUI(): void {
if (chatPanel) {
try {
chatPanel.dispose();
} catch (err) {
console.warn(`${LOG_PREFIX} Error disposing chat panel:`, err);
}
chatPanel = null;
}
if (shadowHost) {
try {
shadowHost.dispose();
} catch (err) {
console.warn(`${LOG_PREFIX} Error disposing shadow host:`, err);
}
shadowHost = null;
}
}
/**
* Show the Quick Panel
*/
function show(): void {
if (disposed) {
console.warn(`${LOG_PREFIX} Cannot show - controller is disposed`);
return;
}
// Already visible
if (chatPanel && shadowHost?.getElements()) {
chatPanel.focusInput();
return;
}
// Clean up any stale UI
disposeUI();
// Create shadow host
shadowHost = mountQuickPanelShadowHost({
hostId: options.hostId,
zIndex: options.zIndex,
});
const elements = shadowHost.getElements();
if (!elements) {
console.error(`${LOG_PREFIX} Failed to create shadow host elements`);
disposeUI();
return;
}
// Ensure bridge is ready
const bridge = ensureBridge();
// Create chat panel
chatPanel = mountQuickPanelAiChatPanel({
mount: elements.root,
agentBridge: bridge,
title: options.title,
subtitle: options.subtitle,
placeholder: options.placeholder,
autoFocus: true,
onRequestClose: () => hide(),
});
}
/**
* Hide the Quick Panel
*/
function hide(): void {
if (disposed) return;
disposeUI();
}
/**
* Toggle Quick Panel visibility
*/
function toggle(): void {
if (disposed) return;
if (isVisible()) {
hide();
} else {
show();
}
}
/**
* Check if panel is currently visible
*/
function isVisible(): boolean {
return chatPanel !== null && shadowHost?.getElements() !== null;
}
/**
* Fully dispose all resources
*/
function dispose(): void {
if (disposed) return;
disposed = true;
disposeUI();
if (agentBridge) {
try {
agentBridge.dispose();
} catch (err) {
console.warn(`${LOG_PREFIX} Error disposing agent bridge:`, err);
}
agentBridge = null;
}
}
return {
show,
hide,
toggle,
isVisible,
dispose,
};
}
// ============================================================
// Re-exports for convenience
// ============================================================
// Core types
export {
DEFAULT_SCOPE,
QUICK_PANEL_SCOPES,
normalizeQuickPanelScope,
parseScopePrefixedQuery,
normalizeSearchQuery,
} from './core/types';
export type {
QuickPanelScope,
QuickPanelScopeDefinition,
QuickPanelView,
ParsedScopeQuery,
QuickPanelIcon,
SearchResult,
ActionTone,
ActionContext,
Action,
SearchQuery,
SearchProviderContext,
SearchProvider,
QuickPanelState,
} from './core/types';
// Agent bridge
export { createAgentBridge } from './core/agent-bridge';
export type {
QuickPanelAgentBridge,
RequestEventListener,
AgentBridgeOptions,
} from './core/agent-bridge';
// UI Components
export {
// Shadow host
mountQuickPanelShadowHost,
// Panel shell (unified container)
mountQuickPanelShell,
// AI Chat
mountQuickPanelAiChatPanel,
createQuickPanelMessageRenderer,
// Search UI
createSearchInput,
createQuickEntries,
// Styles
QUICK_PANEL_STYLES,
} from './ui';
export type {
// Shadow host
QuickPanelShadowHostElements,
QuickPanelShadowHostManager,
QuickPanelShadowHostOptions,
// Panel shell
QuickPanelShellElements,
QuickPanelShellManager,
QuickPanelShellOptions,
// AI Chat
QuickPanelAiChatPanelManager,
QuickPanelAiChatPanelOptions,
QuickPanelAiChatPanelState,
QuickPanelMessageRenderer,
QuickPanelMessageRendererOptions,
// Search input
SearchInputManager,
SearchInputOptions,
SearchInputState,
// Quick entries
QuickEntriesManager,
QuickEntriesOptions,
} from './ui';
// Search Engine
export { SearchEngine } from './core/search-engine';
export type {
SearchEngineOptions,
SearchEngineRequest,
SearchEngineResponse,
SearchProviderError,
} from './core/search-engine';
// Search Providers
export { createTabsProvider } from './providers';
export type { TabsProviderOptions, TabsSearchResultData } from './providers';