diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 5b38084e30cfa..5a9f2ecf036c1 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -263,6 +263,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private editorPartView!: ISerializableView; private statusBarPartView!: ISerializableView; private pearOverlayPartView!: ISerializableView; + private agentOverlayPartView!: ISerializableView; private environmentService!: IBrowserWorkbenchEnvironmentService; private extensionService!: IExtensionService; @@ -1489,6 +1490,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi const sideBar = this.getPart(Parts.SIDEBAR_PART); const statusBar = this.getPart(Parts.STATUSBAR_PART); const pearOverlayPart = this.getPart(Parts.PEAROVERLAY_PART); + const agentOverlayPart = this.getPart(Parts.AGENTOVERLAY_PART); // View references for all parts this.titleBarPartView = titleBar; @@ -1500,6 +1502,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.auxiliaryBarPartView = auxiliaryBarPart; this.statusBarPartView = statusBar; this.pearOverlayPartView = pearOverlayPart; + this.agentOverlayPartView = agentOverlayPart; // Create a new container for PearOverlayPart const pearOverlayPartContainer = document.createElement("div"); @@ -1516,6 +1519,21 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi this.mainContainer.appendChild(pearOverlayPartContainer); pearOverlayPart.create(pearOverlayPartContainer); + // Create a new container for AgentOverlayPart + const agentOverlayPartContainer = document.createElement("div"); + agentOverlayPartContainer.style.position = "absolute"; + agentOverlayPartContainer.style.top = "0"; + agentOverlayPartContainer.style.left = "0"; + agentOverlayPartContainer.style.right = "0"; + agentOverlayPartContainer.style.bottom = "0"; + agentOverlayPartContainer.style.zIndex = "-10"; + agentOverlayPartContainer.style.display = "absolute"; + agentOverlayPartContainer.classList.add("pearoverlay-part-container"); + agentOverlayPartContainer.style.backgroundColor = 'transparent'; + + this.mainContainer.appendChild(agentOverlayPartContainer); + agentOverlayPart.create(agentOverlayPartContainer); + const viewMap = { [Parts.ACTIVITYBAR_PART]: this.activityBarPartView, [Parts.BANNER_PART]: this.bannerPartView, @@ -1526,6 +1544,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi [Parts.STATUSBAR_PART]: this.statusBarPartView, [Parts.AUXILIARYBAR_PART]: this.auxiliaryBarPartView, [Parts.PEAROVERLAY_PART]: this.pearOverlayPartView, + [Parts.AGENTOVERLAY_PART]: this.agentOverlayPartView }; const fromJSON = ({ type }: { type: Parts }) => viewMap[type]; diff --git a/src/vs/workbench/browser/parts/overlayAgent/agentOverlayActions.ts b/src/vs/workbench/browser/parts/overlayAgent/agentOverlayActions.ts new file mode 100644 index 0000000000000..aff3d202d6704 --- /dev/null +++ b/src/vs/workbench/browser/parts/overlayAgent/agentOverlayActions.ts @@ -0,0 +1,147 @@ +// import { registerAction2, Action2 } from "../../../../platform/actions/common/actions.js"; +// import { ServicesAccessor } from "../../../../platform/instantiation/common/instantiation.js"; +// import { IAgentOverlayService } from "./agentOverlayService.js"; +// import { KeyCode, KeyMod } from "../../../../base/common/keyCodes.js"; +// import { IStorageService } from '../../../../platform/storage/common/storage.js'; +// import { INotificationService, Severity } from '../../../../platform/notification/common/notification.js'; +// import { ICommandService } from '../../../../platform/commands/common/commands.js'; + + +// const PEARAI_FIRST_LAUNCH_KEY = "testkey" + +// export class CloseAgentOverlayAction extends Action2 { +// static readonly ID = "workbench.action.closeAgentOverlay"; + +// constructor() { +// super({ +// id: CloseAgentOverlayAction.ID, +// title: { value: "Close Agent Popup", original: "Close Agent Popup" }, +// f1: true, +// keybinding: { +// weight: 200, +// primary: KeyCode.Escape, +// }, +// }); +// } + +// run(accessor: ServicesAccessor): void { +// const pearaiOverlayService = accessor.get(IPearOverlayService); +// pearaiOverlayService.hide(); +// } +// } + +// export class TogglePearOverlayAction extends Action2 { +// static readonly ID = "workbench.action.togglePearAI"; + +// constructor() { +// super({ +// id: TogglePearOverlayAction.ID, +// title: { value: "Toggle PearAI Popup", original: "Toggle PearAI Popup" }, +// f1: true, +// keybinding: { +// weight: 200, +// primary: KeyMod.CtrlCmd | KeyCode.KeyE, +// }, +// }); +// } + +// run(accessor: ServicesAccessor): void { +// const pearaiOverlayService = accessor.get(IPearOverlayService); +// pearaiOverlayService.toggle(); +// } +// } + +// export class MarkPearAIFirstLaunchCompleteAction extends Action2 { +// static readonly ID = "workbench.action.markPearAIFirstLaunchComplete"; + +// constructor() { +// super({ +// id: MarkPearAIFirstLaunchCompleteAction.ID, +// title: { value: "Mark PearAI First Launch Key Complete", original: "Mark PearAI First Launch Key Complete" }, +// f1: true, +// }); +// } + +// run(accessor: ServicesAccessor): void { +// const storageService = accessor.get(IStorageService); +// storageService.store(PEARAI_FIRST_LAUNCH_KEY, true, 0, 0); +// // const notificationService = accessor.get(INotificationService); +// // const commandService = accessor.get(ICommandService); // Get command service early +// // notificationService.notify({ +// // severity: Severity.Info, +// // message: 'Successfully marked PearAI first launch Key complete', +// // actions: { +// // primary: [{ +// // id: 'reloadWindow', +// // label: 'Reload Window', +// // tooltip: 'Reload Window', +// // class: '', +// // enabled: true, +// // run: () => { +// // commandService.executeCommand('workbench.action.reloadWindow'); +// // } +// // }] +// // } +// // }); +// } +// } + +// export class ResetPearAIFirstLaunchKeyAction extends Action2 { +// static readonly ID = "workbench.action.resetPearAIFirstLaunchKey"; + +// constructor() { +// super({ +// id: ResetPearAIFirstLaunchKeyAction.ID, +// title: { value: "Reset PearAI First Launch Key", original: "Reset PearAI First Launch Key" }, +// f1: true, +// }); +// } + +// run(accessor: ServicesAccessor): void { +// const storageService = accessor.get(IStorageService); +// const notificationService = accessor.get(INotificationService); +// const commandService = accessor.get(ICommandService); // Get command service early + +// storageService.store(PEARAI_FIRST_LAUNCH_KEY, false, 0, 0); +// notificationService.notify({ +// severity: Severity.Info, +// message: 'Successfully reset PearAI first launch Key', +// actions: { +// primary: [{ +// id: 'reloadWindow', +// label: 'Reload Window', +// tooltip: 'Reload Window', +// class: '', +// enabled: true, +// run: () => { +// commandService.executeCommand('workbench.action.reloadWindow'); +// } +// }] +// } +// }); +// } +// } + +// export class IsPearAIFirstLaunchAction extends Action2 { +// static readonly ID = "workbench.action.isPearAIFirstLaunch"; + +// constructor() { +// super({ +// id: IsPearAIFirstLaunchAction.ID, +// title: { value: "Is PearAI First Launch", original: "Is PearAI First Launch" }, +// f1: true, +// }); +// } + +// run(accessor: ServicesAccessor): boolean | undefined { +// const storageService = accessor.get(IStorageService); +// return !storageService.getBoolean(PEARAI_FIRST_LAUNCH_KEY, 0); +// } +// } + +// // registerAction2(TogglePearOverlayAction); +// // registerAction2(CloseAgentOverlayAction); + +// // registerAction2(MarkPearAIFirstLaunchCompleteAction); +// // registerAction2(ResetPearAIFirstLaunchKeyAction); +// // registerAction2(IsPearAIFirstLaunchAction); diff --git a/src/vs/workbench/browser/parts/overlayAgent/agentOverlayPart.ts b/src/vs/workbench/browser/parts/overlayAgent/agentOverlayPart.ts new file mode 100644 index 0000000000000..1f004a74d1f2e --- /dev/null +++ b/src/vs/workbench/browser/parts/overlayAgent/agentOverlayPart.ts @@ -0,0 +1,433 @@ +/* eslint-disable header/header */ + +// @Himanshu: This Overlay layout is messed up. +// its not maintainable and iterable. +// Simplyfy this. +// why open and show are two different functions? +// extract out the styles into css files. +// fullscreen? container? webview? popupAreaOverlay? should be just one thing. +// display, opacity, z-index, transition, etc. +// this should be just skeleton, full control should be inside submodule for layout. + +import { Part } from "../../../../workbench/browser/part.js"; +import { + IWorkbenchLayoutService, + Parts, +} from "../../../../workbench/services/layout/browser/layoutService.js"; +import { IThemeService } from "../../../../platform/theme/common/themeService.js"; +import { IStorageService } from "../../../../platform/storage/common/storage.js"; +import { $, getActiveWindow } from "../../../../base/browser/dom.js"; +import { CancellationTokenSource } from "../../../../base/common/cancellation.js"; +import { IInstantiationService } from "../../../../platform/instantiation/common/instantiation.js"; +import { WebviewExtensionDescription } from "../../../../workbench/contrib/webview/browser/webview.js"; + +import { + IWebviewViewService, + WebviewView, +} from "../../../../workbench/contrib/webviewView/browser/webviewViewService.js"; +import { WebviewService } from "../../../../workbench/contrib/webview/browser/webviewService.js"; +import { URI } from "../../../../base/common/uri.js"; +import { ExtensionIdentifier } from "../../../../platform/extensions/common/extensions.js"; +import { IEditorGroupsService } from "../../../../workbench/services/editor/common/editorGroupsService.js"; + +const PEARAI_FIRST_LAUNCH_KEY = "testkey"; + +const PEARAI_CHAT_ID = "pearai.chatView"; +const PEAR_OVERLAY_TITLE = "pearai.overlayView"; + +export class AgentOverlayPart extends Part { + static readonly ID = "workbench.parts.agentoverlay"; + + readonly minimumWidth: number = 300; + readonly maximumWidth: number = 800; + readonly minimumHeight: number = 200; + readonly maximumHeight: number = 600; + + private fullScreenOverlay: HTMLElement | undefined; + private popupAreaOverlay: HTMLElement | undefined; + private webviewView: WebviewView | undefined; + private _webviewService: WebviewService | undefined; + + private state: "loading" | "open" | "closed" = "loading"; + private _isLocked: boolean = false; + private loadingOverlay: HTMLElement | undefined; + private isExtensionReady: boolean = false; + // private _storageService: IStorageService; + private isFirstLaunch: boolean; + + constructor( + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService, + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @IWebviewViewService + private readonly _webviewViewService: IWebviewViewService, + @IInstantiationService + private readonly _instantiationService: IInstantiationService, + @IEditorGroupsService + private readonly _editorGroupsService: IEditorGroupsService, + ) { + super( + AgentOverlayPart.ID, + { hasTitle: false }, + themeService, + storageService, + layoutService, + ); + this.isFirstLaunch = !storageService.getBoolean(PEARAI_FIRST_LAUNCH_KEY, 0); + + // this._storageService = storageService; + this._webviewService = + this._instantiationService.createInstance(WebviewService); + + this.initialize(); + } + + isVisible(): boolean { + return this.state === "open"; + } + + private async initialize() { + // Only set initial state to open if it's first launch + console.log("I AM HERE", this.isFirstLaunch); + if (this.isFirstLaunch) { + this.state = "open"; + this.lock(); + } else { + this.state = "closed"; + } + + const extensionDescription: WebviewExtensionDescription = { + id: new ExtensionIdentifier(PEARAI_CHAT_ID), + location: URI.parse(""), + }; + + // 1. create an IOverlayWebview + const webview = this._webviewService!.createWebviewOverlay({ + title: PEAR_OVERLAY_TITLE, + options: { + enableFindWidget: false, + }, + contentOptions: { + allowScripts: true, + localResourceRoots: [], + }, + extension: extensionDescription, + }); + + // Ensure the overlay is visible immediately + webview.container.style.display = this.isFirstLaunch ? "flex" : "none"; + webview.container.style.opacity = this.isFirstLaunch ? "1" : "0"; + webview.container.style.zIndex = this.isFirstLaunch ? "1000" : "-1"; // Ensure proper z-index on first launch + webview.container.style.transition = "opacity 0.3s ease-in"; + webview.container.style.position = "absolute"; // Ensure proper stacking + + webview.claim(this, getActiveWindow(), undefined); + + // 2. initialize this.webviewView by creating a WebviewView + this.webviewView = { + webview, + onDidChangeVisibility: () => { + return { dispose: () => {} }; + }, + onDispose: () => { + return { dispose: () => {} }; + }, + + get title(): string | undefined { + return PEAR_OVERLAY_TITLE; + }, + set title(value: string | undefined) {}, + + get description(): string | undefined { + return undefined; + }, + set description(value: string | undefined) {}, + + get badge() { + return undefined; + }, + set badge(badge) {}, + + dispose: () => {}, + + show: (preserveFocus) => {}, + }; + + // 3. ask the webviewViewService to connect our webviewView to the webviewViewProvider, PearInventoryPanel + const source = new CancellationTokenSource(); // todo add to disposables + await this._webviewViewService.resolve( + PEARAI_CHAT_ID, + this.webviewView!, + source.token, + ); + + // if both content and webview are ready, end loading state and open + if (this.popupAreaOverlay && this.webviewView) { + this.webviewView.webview.layoutWebviewOverElement(this.popupAreaOverlay); + // Only open on first launch + if (this.isFirstLaunch) { + this.open(); + } + } else { + // Show loading overlay only if it's first launch + if (this.isFirstLaunch && this.loadingOverlay) { + this.loadingOverlay.style.display = "flex"; + } + } + + // Set initial visibility of webview container based on first launch + webview.container.style.display = this.isFirstLaunch ? "flex" : "none"; + webview.container.style.opacity = this.isFirstLaunch ? "1" : "0"; + webview.container.style.transition = "opacity 0.3s ease-in"; + } + + protected override createContentArea(element: HTMLElement): HTMLElement { + // create the full screen overlay. this serves as a click target for closing pearai + this.element = element; + this.fullScreenOverlay = element; // use the pearOverlayPart root element as the fullScreenOverlay + this.fullScreenOverlay.style.zIndex = this.isFirstLaunch ? "95" : "-10"; // Only show on first launch + this.fullScreenOverlay.style.position = "absolute"; + this.fullScreenOverlay.style.top = "0"; + this.fullScreenOverlay.style.left = "0"; + this.fullScreenOverlay.style.right = "0"; + this.fullScreenOverlay.style.bottom = "0"; + this.fullScreenOverlay.style.backgroundColor = "rgba(0, 0, 0, 0.5)"; + // this.fullScreenOverlay.style.pointerEvents = "none"; // Ignore clicks on the full screen overlay + this.fullScreenOverlay!.style.backgroundColor = "rgba(0, 0, 0, 0.5)"; // Darken the overlay + + // create the popup area overlay. this is just a target for webview to layout over + this.popupAreaOverlay = $("div.pearai-popup-area-overlay"); + this.popupAreaOverlay.style.position = "absolute"; + this.popupAreaOverlay.style.margin = "0"; + this.popupAreaOverlay.style.top = "0"; + this.popupAreaOverlay.style.left = "0"; + this.popupAreaOverlay.style.right = "0"; + this.popupAreaOverlay.style.bottom = "0"; + this.element.appendChild(this.popupAreaOverlay); + + if (this.isFirstLaunch) { + // Create loading overlay with higher z-index and pointer-events handling + this.loadingOverlay = $('div.pearai-loading-overlay'); + this.loadingOverlay.style.position = 'fixed'; // Change to fixed positioning + this.loadingOverlay.style.top = '0'; + this.loadingOverlay.style.left = '0'; + this.loadingOverlay.style.right = '0'; + this.loadingOverlay.style.bottom = '0'; + this.loadingOverlay.style.backgroundColor = 'var(--vscode-editor-background)'; + this.loadingOverlay.style.zIndex = '9999'; // Much higher z-index + this.loadingOverlay.style.display = 'flex'; + this.loadingOverlay.style.alignItems = 'center'; + this.loadingOverlay.style.justifyContent = 'center'; + this.loadingOverlay.style.pointerEvents = 'all'; // Ensure it blocks interactions + + const loadingText = $('div.loading-text'); + loadingText.textContent = 'this is agent overlay...'; + loadingText.style.color = '#839497'; + loadingText.style.fontSize = '20px'; + // loadingText.addEventListener('click', () => { + // this.hideOverlayLoadingMessage(); + // }); + + this.loadingOverlay.appendChild(loadingText); + this.element.appendChild(this.loadingOverlay); + } + + // // Add message listener to webview for extension ready event + // this.webviewView?.webview.onMessage(message => { + // if (message.type === 'extension-ready') { + // this.hideLoadingOverlay(); + // } + // }); + + // if both content and webview are ready, end loading state and open + if (this.popupAreaOverlay && this.webviewView) { + this.webviewView.webview.layoutWebviewOverElement(this.popupAreaOverlay); + // Only open on first launch + if (this.isFirstLaunch) { + this.open(); + } + else { + // createContentArea is called within the workbench and layout when instantiating the overlay. + // If we don't close it here, it will open up by default when editor starts, or appear for half a second. + // If we remove this completely, it gets stuck in the loading stage, so we must close it. + this.close(); + } + } else { + // Show loading overlay only if it's first launch + if (this.isFirstLaunch && this.loadingOverlay) { + this.loadingOverlay.style.display = "flex"; + } + } + + return this.fullScreenOverlay!; + } + + override layout( + width: number, + height: number, + top: number, + left: number, + ): void { + super.layout(width, height, top, left); + if (this.fullScreenOverlay) { + this.fullScreenOverlay!.style.width = `${width}px`; + this.fullScreenOverlay!.style.height = `${height}px`; + } + + if (this.popupAreaOverlay) { + this.popupAreaOverlay.style.width = `${width}px`; + this.popupAreaOverlay.style.height = `${height}px`; + this.popupAreaOverlay.style.backgroundColor = "transparent"; + this.popupAreaOverlay.style.borderRadius = "12px"; + } + + if (this.state === "open") { + this.webviewView!.webview.layoutWebviewOverElement( + this.popupAreaOverlay!, + ); + } + } + + private open() { + if (this.state === "open") { + return; + } + this.state = "open"; + this.fullScreenOverlay!.style.zIndex = "95"; + + const container = this.webviewView!.webview.container; + container.style.display = "flex"; + container.style.zIndex = "1000"; + container.style.display = 'flex'; + container.style.opacity = '1'; + + // Show loading overlay if extension is not ready + if (!this.isExtensionReady && this.loadingOverlay) { + this.loadingOverlay.style.display = "flex"; + } + + this.fullScreenOverlay?.addEventListener("click", () => { + // TODO: If we are in the tutorial, don't close + this.close(); + }); + + this.webviewView!.webview.layoutWebviewOverElement(this.popupAreaOverlay!); + this.focus(); + } + + private close() { + if (this.isLocked) { + return; // Prevent closing when locked + } + + if (this.state === "closed") { + return; + } + this.state = "closed"; + const container = this.webviewView!.webview.container; + + // Apply fade-out animation + container.style.animation = "pearaiFadeOut 0.2s ease-out"; + + // Hide elements after animation completes + setTimeout(() => { + this.fullScreenOverlay!.style.zIndex = "-10"; + container.style.display = "none"; + + // Focus the active editor + this._editorGroupsService.activeGroup.focus(); + }, 20); // 20ms matches the animation duration + } + + private toggleOpenClose() { + this.state === "open" ? this.close() : this.open(); + } + + focus(): void { + if (this.webviewView) { + this.webviewView.webview.focus(); + } + } + + show(): void { + if (this.state === "loading") { + console.warn("Can't open PearAI while loading"); + return; + } + + this.open(); + } + + hide(): void { + if (this.state === "loading") { + console.warn("Can't close PearAI while loading"); + return; + } + this.close(); + } + + toggle(): void { + if (this.state === "loading") { + console.warn("Can't toggle PearAI while loading"); + return; + } + this.toggleOpenClose(); + } + + public lock(): void { + this._isLocked = true; + } + + public unlock(): void { + this._isLocked = false; + } + + public get isLocked(): boolean { + return this._isLocked; + } + + public hideOverlayLoadingMessage(): void { + if (this.loadingOverlay) { + // Start fade out of loading overlay + this.loadingOverlay.style.transition = 'all 0.3s ease-out'; + this.loadingOverlay.style.opacity = '0'; + this.loadingOverlay.style.pointerEvents = 'none'; + + // Only show webview if we're in the "open" state + const container = this.webviewView!.webview.container; + if (this.state === "open") { + // Ensure proper z-index stacking + container.style.zIndex = '1000'; + this.fullScreenOverlay!.style.zIndex = '95'; + + container.style.display = 'flex'; + container.style.opacity = '0'; + container.style.transition = 'opacity 0.3s ease-in'; + + // Slight delay to ensure smooth transition + setTimeout(() => { + container.style.opacity = '1'; + }, 50); + } else { + container.style.display = 'none'; + container.style.opacity = '0'; + this.fullScreenOverlay!.style.zIndex = '-10'; + } + + // Clean up after animations complete + setTimeout(() => { + if (this.loadingOverlay) { + this.loadingOverlay.style.display = 'none'; + this.loadingOverlay.style.zIndex = '-1'; // Move it below everything + this.isExtensionReady = true; + } + }, 300); + } + } + + toJSON(): object { + return { + type: Parts.AGENTOVERLAY_PART, + }; + } +} diff --git a/src/vs/workbench/browser/parts/overlayAgent/agentOverlayService.ts b/src/vs/workbench/browser/parts/overlayAgent/agentOverlayService.ts new file mode 100644 index 0000000000000..ef1142bcc0d34 --- /dev/null +++ b/src/vs/workbench/browser/parts/overlayAgent/agentOverlayService.ts @@ -0,0 +1,195 @@ +/* eslint-disable header/header */ + +import { + registerSingleton, + InstantiationType, +} from "../../../../platform/instantiation/common/extensions.js"; +import { Disposable, IDisposable } from "../../../../base/common/lifecycle.js"; +import { AgentOverlayPart } from "./agentOverlayPart.js"; +import { + createDecorator, + IInstantiationService, +} from "../../../../platform/instantiation/common/instantiation.js"; +import { IEditorService } from "../../../../workbench/services/editor/common/editorService.js"; +import { ITerminalService } from "../../../../workbench/contrib/terminal/browser/terminal.js"; +import { CommandsRegistry } from "../../../../platform/commands/common/commands.js"; + +export const IAgentOverlayService = createDecorator( + "agentOverlayService", +); + +export interface IAgentOverlayService extends IDisposable { + readonly _serviceBrand: undefined; + + /** + * Returns the PearOverlayPart instance. + */ + readonly agentOverlayPart: AgentOverlayPart; + + /** + * Shows the PearAI popup. + */ + show(): void; + + /** + * Hides the PearAI popup. + */ + hide(): void; + + /** + * Toggles the visibility of the PearAI popup. + */ + toggle(): void; + + /** + * Returns true if the PearAI popup is visible. + */ + isVisible(): boolean; + + /** + * Locks the PearAI popup. + */ + lock(): void; + + /** + * Unlocks the PearAI popup. + */ + unlock(): void; + + /** + * Returns true if the PearAI popup is locked. + */ + isLocked(): boolean; + + /** + * Hides the loading overlay message. + */ + hideOverlayLoadingMessage(): void; +} + +export class AgentOverlayService + extends Disposable + implements IAgentOverlayService +{ + declare readonly _serviceBrand: undefined; + + private readonly _agentOverlayPart: AgentOverlayPart; + + constructor( + @IInstantiationService + private readonly instantiationService: IInstantiationService, + @IEditorService private readonly _editorService: IEditorService, + @ITerminalService private readonly _terminalService: ITerminalService, + // @ICommandService private readonly commandService: ICommandService, + ) { + super(); + this._agentOverlayPart = + this.instantiationService.createInstance(AgentOverlayPart); + this.registerListeners(); + this.registerCommands(); + } + + private registerListeners(): void { + this._register( + this._editorService.onDidActiveEditorChange(() => { + this.hide(); + }), + ); + + this._register( + this._terminalService.onDidFocusInstance(() => { + this.hide(); + }), + ); + } + + private registerCommands(): void { + // Register commands for external use e.g. in pearai submodule + CommandsRegistry.registerCommand("pearai.isAgentOverlayVisible", (accessor) => { + const overlayService = accessor.get(IAgentOverlayService); + return overlayService.isVisible(); + }); + + CommandsRegistry.registerCommand("pearai.showAgentOverlay", (accessor) => { + const overlayService = accessor.get(IAgentOverlayService); + overlayService.show(); + }); + + CommandsRegistry.registerCommand("pearai.hideAgentOverlay", (accessor) => { + const overlayService = accessor.get(IAgentOverlayService); + overlayService.hide(); + }); + + CommandsRegistry.registerCommand("pearai.toggleAgentOverlay", (accessor) => { + const overlayService = accessor.get(IAgentOverlayService); + overlayService.toggle(); + }); + + CommandsRegistry.registerCommand("pearai.lockAgentOverlay", (accessor) => { + const overlayService = accessor.get(IAgentOverlayService); + overlayService.lock(); + }); + + CommandsRegistry.registerCommand("pearai.unlockAgentOverlay", (accessor) => { + const overlayService = accessor.get(IAgentOverlayService); + overlayService.unlock(); + }); + + CommandsRegistry.registerCommand("pearai.isAgentOverlayLocked", (accessor) => { + const overlayService = accessor.get(IAgentOverlayService); + return overlayService.isLocked(); + }); + + CommandsRegistry.registerCommand("pearai.hideAgentOverlayLoadingMessage", (accessor) => { + const overlayService = accessor.get(IAgentOverlayService); + overlayService.hideOverlayLoadingMessage(); + }); + } + + get agentOverlayPart(): AgentOverlayPart { + return this._agentOverlayPart; + } + + show(): void { + this._agentOverlayPart.show(); + } + + hide(): void { + this._agentOverlayPart.hide(); + } + + hideOverlayLoadingMessage(): void { + this._agentOverlayPart.hideOverlayLoadingMessage(); + } + + toggle(): void { + this._agentOverlayPart.toggle(); + } + + lock(): void { + this._agentOverlayPart.lock(); + } + + unlock(): void { + this._agentOverlayPart.unlock(); + } + + isLocked(): boolean { + return this._agentOverlayPart.isLocked; + } + + override dispose(): void { + super.dispose(); + this._agentOverlayPart.dispose(); + } + + isVisible(): boolean { + return this._agentOverlayPart.isVisible(); + } +} + +registerSingleton( + IAgentOverlayService, + AgentOverlayService, + InstantiationType.Eager, +); diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index 344be475e4c29..60af6de40dbe1 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -51,6 +51,7 @@ import { AccessibleViewRegistry } from '../../platform/accessibility/browser/acc import { NotificationAccessibleView } from './parts/notifications/notificationAccessibleView.js'; import { IPearOverlayService } from '../browser/parts/overlay/pearOverlayService.js'; import { IShadowOverlayService } from './parts/overlay/onboardingShadow/shadowOverlayService.js'; +import { IAgentOverlayService } from './parts/overlayAgent/agentOverlayService.js'; export interface IWorkbenchOptions { @@ -344,7 +345,8 @@ export class Workbench extends Layout { { id: Parts.PANEL_PART, role: 'none', classes: ['panel', 'basepanel', positionToString(this.getPanelPosition())] }, { id: Parts.AUXILIARYBAR_PART, role: 'none', classes: ['auxiliarybar', 'basepanel', this.getSideBarPosition() === Position.LEFT ? 'right' : 'left'] }, { id: Parts.STATUSBAR_PART, role: 'status', classes: ['statusbar'] }, - { id: Parts.PEAROVERLAY_PART, role: 'none', classes: [] } + { id: Parts.PEAROVERLAY_PART, role: 'none', classes: [] }, + { id: Parts.AGENTOVERLAY_PART, role: 'none', classes: [] }, ]) { const partContainer = this.createPart(id, role, classes); @@ -354,6 +356,12 @@ export class Workbench extends Layout { }); } + if (id === Parts.AGENTOVERLAY_PART) { + instantiationService.invokeFunction(accessor => { + accessor.get(IAgentOverlayService); + }); + } + // instantiate highlighting instantiationService.invokeFunction(accessor => { accessor.get(IShadowOverlayService); diff --git a/src/vs/workbench/services/layout/browser/layoutService.ts b/src/vs/workbench/services/layout/browser/layoutService.ts index 1fd84451372f2..c54e10dc9dd15 100644 --- a/src/vs/workbench/services/layout/browser/layoutService.ts +++ b/src/vs/workbench/services/layout/browser/layoutService.ts @@ -27,7 +27,8 @@ export const enum Parts { AUXILIARYBAR_PART = 'workbench.parts.auxiliarybar', EDITOR_PART = 'workbench.parts.editor', STATUSBAR_PART = 'workbench.parts.statusbar', - PEAROVERLAY_PART = 'workbench.parts.pearoverlay' + PEAROVERLAY_PART = 'workbench.parts.pearoverlay', + AGENTOVERLAY_PART = 'workbench.parts.agentoverlay', } export const enum ZenModeSettings {