Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/vs/platform/native/common/native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ export interface ICommonNativeHostService {
openWindow(options?: IOpenEmptyWindowOptions): Promise<void>;
openWindow(toOpen: IWindowOpenable[], options?: IOpenWindowOptions): Promise<void>;

openAgentsWindow(): Promise<void>;
openAgentsWindow(options?: { readonly forceNewWindow?: boolean }): Promise<void>;

isFullScreen(options?: INativeHostOptions): Promise<boolean>;
toggleFullScreen(options?: INativeHostOptions): Promise<void>;
Expand Down
5 changes: 3 additions & 2 deletions src/vs/platform/native/electron-main/nativeHostMainService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,11 +304,12 @@ export class NativeHostMainService extends Disposable implements INativeHostMain
}, options);
}

async openAgentsWindow(windowId: number | undefined): Promise<void> {
async openAgentsWindow(windowId: number | undefined, options?: { readonly forceNewWindow?: boolean }): Promise<void> {
await this.windowsMainService.openAgentsWindow({
context: OpenContext.API,
contextWindowId: windowId,
cli: this.environmentMainService.args
cli: this.environmentMainService.args,
forceNewWindow: options?.forceNewWindow,
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
context: openConfig.context,
contextWindowId: openConfig.contextWindowId,
initialStartup: openConfig.initialStartup,
forceNewWindow: openConfig.forceNewWindow,
};
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { $, addDisposableListener } from '../../../../../base/browser/dom.js';
import { DisposableStore } from '../../../../../base/common/lifecycle.js';
import { localize } from '../../../../../nls.js';
import { ICommandService, CommandsRegistry } from '../../../../../platform/commands/common/commands.js';
import { IProductService } from '../../../../../platform/product/common/productService.js';
import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';

const OPEN_AGENTS_WINDOW_COMMAND = 'workbench.action.openAgentsWindow';

type AgentsBannerClickedEvent = {
source: string;
action: string;
};

type AgentsBannerClickedClassification = {
source: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Where the banner was clicked from.' };
action: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The action taken on the banner.' };
owner: 'benibenj';
comment: 'Tracks clicks on the agents app banner across welcome pages.';
};

export interface IAgentsBannerResult {
readonly element: HTMLElement;
readonly disposables: DisposableStore;
}

/**
* Returns whether the agents banner can be shown.
* The banner requires the `workbench.action.openAgentsWindow` command
* to be registered (desktop builds only) and is limited to Insiders quality.
*/
export function canShowAgentsBanner(productService: IProductService): boolean {
return productService.quality !== 'stable'
&& !!CommandsRegistry.getCommand(OPEN_AGENTS_WINDOW_COMMAND);
}

export interface IAgentsBannerOptions {
/** Dot-separated CSS classes for the banner container (e.g. 'my-banner' or 'foo.bar'). */
readonly cssClass: string;
/** Identifies where the banner is displayed (e.g. 'welcomePage', 'agentSessionsWelcome'). */
readonly source: string;
/** Override the default button label. */
readonly label?: string;
/** Optional callback invoked when the banner button is clicked. */
readonly onButtonClick?: () => void;
}

/**
* Creates a banner that promotes the Agents app.
* The banner contains a button that opens the Agents window.
*/
export function createAgentsBanner(
options: IAgentsBannerOptions,
commandService: ICommandService,
telemetryService: ITelemetryService,
): IAgentsBannerResult {
const disposables = new DisposableStore();
const label = options.label ?? localize('agentsBanner.tryAgentsAppLabel', "Try out the new Agents app");

const button = $('button.agents-banner-button', {
title: label,
},
$('.codicon.codicon-agent.icon-widget'),
$('span.category-title', {}, label),
);
disposables.add(addDisposableListener(button, 'click', () => {
options.onButtonClick?.();
telemetryService.publicLog2<AgentsBannerClickedEvent, AgentsBannerClickedClassification>('agentsBanner.clicked', { source: options.source, action: 'openAgentsWindow' });
commandService.executeCommand(OPEN_AGENTS_WINDOW_COMMAND, { forceNewWindow: true });
}));

const element = $(`.${options.cssClass}`, {}, button);

return { element, disposables };
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class OpenAgentsWindowAction extends Action2 {
});
}

async run(accessor: ServicesAccessor) {
async run(accessor: ServicesAccessor, options?: { forceNewWindow?: boolean }) {
const openerService = accessor.get(IOpenerService);
const productService = accessor.get(IProductService);
const environmentService = accessor.get(IWorkbenchEnvironmentService);
Expand All @@ -43,7 +43,7 @@ export class OpenAgentsWindowAction extends Action2 {
await openerService.open(URI.from({ scheme: productService.embedded.urlProtocol, authority: Schemas.file }), { openExternal: true });
} else {
const nativeHostService = accessor.get(INativeHostService);
await nativeHostService.openAgentsWindow();
await nativeHostService.openAgentsWindow({ forceNewWindow: options?.forceNewWindow });
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import { IWorkspaceTrustManagementService } from '../../../../platform/workspace
import { IViewDescriptorService, ViewContainerLocation } from '../../../common/views.js';
import { toErrorMessage } from '../../../../base/common/errorMessage.js';
import { ILogService } from '../../../../platform/log/common/log.js';
import { canShowAgentsBanner, createAgentsBanner } from '../../chat/browser/agentSessions/agentSessionsBanner.js';

const configurationKey = 'workbench.startupEditor';
const MAX_SESSIONS = 6;
Expand Down Expand Up @@ -593,13 +594,21 @@ export class AgentSessionsWelcomePage extends EditorPane {
this.layoutSessionsControl();
}));

// "View all sessions" link
const openButton = append(container, $('button.agentSessionsWelcome-openSessionsButton'));
openButton.textContent = localize('viewAllSessions', "View All Sessions");
openButton.onclick = () => {
this._closedBy = 'viewAllSessions';
this.revealMaximizedChat();
};
// "Try out the new Agents app" banner
if (canShowAgentsBanner(this.productService)) {
const agentsBanner = createAgentsBanner(
{
cssClass: 'agentSessionsWelcome-agentsBanner',
source: 'agentSessionsWelcome',
label: localize('viewAllSessions', "View All Sessions"),
onButtonClick: () => { this._closedBy = 'viewAllSessions'; },
},
this.commandService,
this.telemetryService,
);
this.sessionsControlDisposables.add(agentsBanner.disposables);
append(container, agentsBanner.element);
}
}

private buildWalkthroughs(container: HTMLElement): void {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,25 +210,49 @@
display: none !important;
}

.agentSessionsWelcome-openSessionsButton {
.agentSessionsWelcome-agentsBanner {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
gap: 8px;
width: 100%;
padding: 8px 16px;
margin-top: 12px;
font-size: 13px;
}

.agentSessionsWelcome-agentsBanner .agents-banner-button {
display: flex;
align-items: center;
padding: 6px 10px;
border: none;
border-radius: 4px;
border-radius: 6px;
background-color: var(--vscode-toolbar-hoverBackground);
color: var(--vscode-foreground);
font-size: 13px;
cursor: pointer;
font-family: inherit;
border-radius: 50px;
width: 100%;
justify-content: center;
}

.agentSessionsWelcome-openSessionsButton:hover {
.agentSessionsWelcome-agentsBanner .codicon {
color: var(--vscode-textLink-foreground) !important;
}

.agentSessionsWelcome-agentsBanner .agents-banner-button:hover {
background-color: var(--vscode-toolbar-activeBackground);
color: var(--vscode-textLink-foreground);
}

.agentSessionsWelcome-agentsBanner .icon-widget {
font-size: 20px;
padding-right: 8px;
}

.agentSessionsWelcome-agentsBanner .category-title {
font-size: 13px;
font-weight: normal;
margin: 0;
}

/* Walkthroughs section */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ import { IExtensionService } from '../../../services/extensions/common/extension
import { IHostService } from '../../../services/host/browser/host.js';
import { IWorkbenchThemeService } from '../../../services/themes/common/workbenchThemeService.js';
import { GettingStartedIndexList } from './gettingStartedList.js';
import { canShowAgentsBanner, createAgentsBanner } from '../../chat/browser/agentSessions/agentSessionsBanner.js';
import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js';
import { AccessibleViewAction } from '../../accessibility/browser/accessibleViewActions.js';
import { KeybindingLabel } from '../../../../base/browser/ui/keybindingLabel/keybindingLabel.js';
Expand Down Expand Up @@ -931,11 +932,25 @@ export class GettingStartedPage extends EditorPane {
const recentList = this.buildRecentlyOpenedList();
const gettingStartedList = this.buildGettingStartedWalkthroughsList();

const footer = $('.footer', {},
$('p.showOnStartup', {},
showOnStartupCheckbox.domNode,
showOnStartupLabel,
));
const footerChildren: HTMLElement[] = [];
if (canShowAgentsBanner(this.productService)) {
const agentsBanner = createAgentsBanner(
{
cssClass: 'getting-started-category.agents-banner',
source: 'welcomePage',
},
this.commandService,
this.telemetryService,
);
this.categoriesSlideDisposables.add(agentsBanner.disposables);
footerChildren.push(agentsBanner.element);
}
footerChildren.push($('p.showOnStartup', {},
showOnStartupCheckbox.domNode,
showOnStartupLabel,
));

const footer = $('.footer', {}, ...footerChildren);

const layoutLists = () => {
if (gettingStartedList.itemCount) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -916,6 +916,25 @@
margin: 0;
}

.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories > .gettingStartedCategoriesContainer > .footer > .agents-banner {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
margin-bottom: 12px;
}

.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories > .gettingStartedCategoriesContainer > .footer > .agents-banner .agents-banner-button {
display: flex;
align-items: center;
padding: 6px 12px;
border-radius: 50px;
cursor: pointer;
font-family: inherit;
font-size: 13px;
margin-bottom: 20px;
}

.monaco-workbench .part.editor > .content .gettingStartedContainer .gettingStartedSlideCategories > .gettingStartedCategoriesContainer .index-list.start-container {
min-height: 156px;
margin-bottom: 16px;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ export class TestNativeHostService implements INativeHostService {
throw new Error('Method not implemented.');
}

async openAgentsWindow(): Promise<void> { }
async openAgentsWindow(_options?: { readonly forceNewWindow?: boolean }): Promise<void> { }

async toggleFullScreen(): Promise<void> { }
async isMaximized(): Promise<boolean> { return true; }
Expand Down
Loading