Не подтверждена Коммит 8f1ea102 создал по автору Benjamin Pasero's avatar Benjamin Pasero Зафиксировано автором GitHub
Просмотр файлов

agent sessions - add a view filter action to filter by provider type (#278021)



* agent sessions - add a view filter action to filter by provider type

* Update src/vs/workbench/contrib/chat/browser/agentSessions/agentSessionsViewFilter.ts

Co-authored-by: default avatarCopilot <175728472+Copilot@users.noreply.github.com>

* feedback

---------

Co-authored-by: default avatarCopilot <175728472+Copilot@users.noreply.github.com>
владелец ae77536e
......@@ -223,6 +223,8 @@ export class MenuId {
static readonly TimelineTitle = new MenuId('TimelineTitle');
static readonly TimelineTitleContext = new MenuId('TimelineTitleContext');
static readonly TimelineFilterSubMenu = new MenuId('TimelineFilterSubMenu');
static readonly AgentSessionsTitle = new MenuId('AgentSessionsTitle');
static readonly AgentSessionsFilterSubMenu = new MenuId('AgentSessionsFilterSubMenu');
static readonly AccountsContext = new MenuId('AccountsContext');
static readonly SidebarTitle = new MenuId('SidebarTitle');
static readonly PanelTitle = new MenuId('PanelTitle');
......
......@@ -12,7 +12,7 @@ import { ContextKeyExpr, IContextKeyService } from '../../../../../platform/cont
import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js';
import { IChatSessionsExtensionPoint, IChatSessionsService } from '../../common/chatSessionsService.js';
import { Codicon } from '../../../../../base/common/codicons.js';
import { AgentSessionProviders } from '../agentSessions/agentSessions.js';
import { AgentSessionProviders, getAgentSessionProviderIcon, getAgentSessionProviderName } from '../agentSessions/agentSessions.js';
import { localize, localize2 } from '../../../../../nls.js';
import { CancellationToken } from '../../../../../base/common/cancellation.js';
import { basename, relativePath } from '../../../../../base/common/resources.js';
......@@ -89,13 +89,13 @@ export class ChatContinueInSessionActionItem extends ActionWidgetDropdownActionV
// Continue in Background
const backgroundContrib = contributions.find(contrib => contrib.type === AgentSessionProviders.Background);
if (backgroundContrib && backgroundContrib.canDelegate !== false) {
actions.push(this.toAction(backgroundContrib, instantiationService));
actions.push(this.toAction(AgentSessionProviders.Background, backgroundContrib, instantiationService));
}
// Continue in Cloud
const cloudContrib = contributions.find(contrib => contrib.type === AgentSessionProviders.Cloud);
if (cloudContrib && cloudContrib.canDelegate !== false) {
actions.push(this.toAction(cloudContrib, instantiationService));
actions.push(this.toAction(AgentSessionProviders.Cloud, cloudContrib, instantiationService));
}
// Offer actions to enter setup if we have no contributions
......@@ -109,32 +109,26 @@ export class ChatContinueInSessionActionItem extends ActionWidgetDropdownActionV
};
}
private static toAction(contrib: IChatSessionsExtensionPoint, instantiationService: IInstantiationService): IActionWidgetDropdownAction {
private static toAction(provider: AgentSessionProviders, contrib: IChatSessionsExtensionPoint, instantiationService: IInstantiationService): IActionWidgetDropdownAction {
return {
id: contrib.type,
enabled: true,
icon: contrib.type === AgentSessionProviders.Cloud ? Codicon.cloud : Codicon.collection,
icon: getAgentSessionProviderIcon(provider),
class: undefined,
label: localize('continueSessionIn', "Continue in {0}", getAgentSessionProviderName(provider)),
tooltip: contrib.displayName,
label: contrib.type === AgentSessionProviders.Cloud ?
localize('continueInCloud', "Continue in Cloud") :
localize('continueInBackground', "Continue in Background"),
run: () => instantiationService.invokeFunction(accessor => new CreateRemoteAgentJobAction().run(accessor, contrib))
};
}
private static toSetupAction(type: string, instantiationService: IInstantiationService): IActionWidgetDropdownAction {
const label = type === AgentSessionProviders.Cloud ?
localize('continueInCloud', "Continue in Cloud") :
localize('continueInBackground', "Continue in Background");
private static toSetupAction(provider: AgentSessionProviders, instantiationService: IInstantiationService): IActionWidgetDropdownAction {
return {
id: type,
id: provider,
enabled: true,
icon: type === AgentSessionProviders.Cloud ? Codicon.cloud : Codicon.collection,
icon: getAgentSessionProviderIcon(provider),
class: undefined,
tooltip: label,
label,
label: localize('continueSessionIn', "Continue in {0}", getAgentSessionProviderName(provider)),
tooltip: localize('continueSessionIn', "Continue in {0}", getAgentSessionProviderName(provider)),
run: () => instantiationService.invokeFunction(accessor => {
const commandService = accessor.get(ICommandService);
return commandService.executeCommand(CHAT_SETUP_ACTION_ID);
......
......@@ -12,9 +12,12 @@ import { Disposable } from '../../../../../base/common/lifecycle.js';
import { ThemeIcon } from '../../../../../base/common/themables.js';
import { URI } from '../../../../../base/common/uri.js';
import { localize } from '../../../../../nls.js';
import { MenuId } from '../../../../../platform/actions/common/actions.js';
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js';
import { ChatSessionStatus, IChatSessionItemProvider, IChatSessionsExtensionPoint, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js';
import { AgentSessionProviders } from './agentSessions.js';
import { AgentSessionProviders, getAgentSessionProviderIcon, getAgentSessionProviderName } from './agentSessions.js';
import { AgentSessionsViewFilter } from './agentSessionsViewFilter.js';
//#region Interfaces, Types
......@@ -74,9 +77,11 @@ export function isAgentSessionsViewModel(obj: IAgentSessionsViewModel | IAgentSe
//#endregion
export class AgentSessionsViewModel extends Disposable implements IAgentSessionsViewModel {
export interface IAgentSessionsViewModelOptions {
readonly filterMenuId: MenuId;
}
readonly sessions: IAgentSessionViewModel[] = [];
export class AgentSessionsViewModel extends Disposable implements IAgentSessionsViewModel {
private readonly _onWillResolve = this._register(new Emitter<void>());
readonly onWillResolve = this._onWillResolve.event;
......@@ -87,15 +92,27 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
private readonly _onDidChangeSessions = this._register(new Emitter<void>());
readonly onDidChangeSessions = this._onDidChangeSessions.event;
private _sessions: IAgentSessionViewModel[] = [];
get sessions(): IAgentSessionViewModel[] {
return this._sessions.filter(session => !this.filter.excludes.has(session.provider.chatSessionType));
}
private readonly resolver = this._register(new ThrottledDelayer<void>(100));
private readonly providersToResolve = new Set<string | undefined>();
private readonly filter: AgentSessionsViewFilter;
constructor(
options: IAgentSessionsViewModelOptions,
@IChatSessionsService private readonly chatSessionsService: IChatSessionsService,
@ILifecycleService private readonly lifecycleService: ILifecycleService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
) {
super();
this.filter = this._register(this.instantiationService.createInstance(AgentSessionsViewFilter, { filterMenuId: options.filterMenuId }));
this.registerListeners();
this.resolve(undefined);
......@@ -105,6 +122,7 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
this._register(this.chatSessionsService.onDidChangeItemsProviders(({ chatSessionType: provider }) => this.resolve(provider)));
this._register(this.chatSessionsService.onDidChangeAvailability(() => this.resolve(undefined)));
this._register(this.chatSessionsService.onDidChangeSessionItems(provider => this.resolve(provider)));
this._register(this.filter.onDidChange(() => this._onDidChangeSessions.fire()));
}
async resolve(provider: string | string[] | undefined): Promise<void> {
......@@ -142,7 +160,7 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
const newSessions: IAgentSessionViewModel[] = [];
for (const provider of this.chatSessionsService.getAllChatSessionItemProviders()) {
if (!providersToResolve.includes(undefined) && !providersToResolve.includes(provider.chatSessionType)) {
newSessions.push(...this.sessions.filter(session => session.provider.chatSessionType === provider.chatSessionType));
newSessions.push(...this._sessions.filter(session => session.provider.chatSessionType === provider.chatSessionType));
continue; // skipped for resolving, preserve existing ones
}
......@@ -172,17 +190,17 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
let icon: ThemeIcon;
let providerLabel: string;
switch ((provider.chatSessionType)) {
case localChatSessionType:
providerLabel = localize('chat.session.providerLabel.local', "Local");
icon = Codicon.vm;
case AgentSessionProviders.Local:
providerLabel = getAgentSessionProviderName(AgentSessionProviders.Local);
icon = getAgentSessionProviderIcon(AgentSessionProviders.Local);
break;
case AgentSessionProviders.Background:
providerLabel = localize('chat.session.providerLabel.background', "Background");
icon = Codicon.collection;
providerLabel = getAgentSessionProviderName(AgentSessionProviders.Background);
icon = getAgentSessionProviderIcon(AgentSessionProviders.Background);
break;
case AgentSessionProviders.Cloud:
providerLabel = localize('chat.session.providerLabel.cloud', "Cloud");
icon = Codicon.cloud;
providerLabel = getAgentSessionProviderName(AgentSessionProviders.Cloud);
icon = getAgentSessionProviderIcon(AgentSessionProviders.Cloud);
break;
default: {
providerLabel = mapSessionContributionToType.get(provider.chatSessionType)?.name ?? provider.chatSessionType;
......@@ -208,8 +226,8 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
}
}
this.sessions.length = 0;
this.sessions.push(...newSessions);
this._sessions.length = 0;
this._sessions.push(...newSessions);
this._onDidChangeSessions.fire();
}
......
......@@ -3,6 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from '../../../../../nls.js';
import { Codicon } from '../../../../../base/common/codicons.js';
import { ThemeIcon } from '../../../../../base/common/themables.js';
import { localChatSessionType } from '../../common/chatSessionsService.js';
export const AGENT_SESSIONS_VIEW_CONTAINER_ID = 'workbench.viewContainer.agentSessions';
......@@ -13,3 +16,26 @@ export enum AgentSessionProviders {
Background = 'copilotcli',
Cloud = 'copilot-cloud-agent',
}
export function getAgentSessionProviderName(provider: AgentSessionProviders): string {
switch (provider) {
case AgentSessionProviders.Local:
return localize('chat.session.providerLabel.local', "Local");
case AgentSessionProviders.Background:
return localize('chat.session.providerLabel.background', "Background");
case AgentSessionProviders.Cloud:
return localize('chat.session.providerLabel.cloud', "Cloud");
}
}
export function getAgentSessionProviderIcon(provider: AgentSessionProviders): ThemeIcon {
switch (provider) {
case AgentSessionProviders.Local:
return Codicon.vm;
case AgentSessionProviders.Background:
return Codicon.collection;
case AgentSessionProviders.Cloud:
return Codicon.cloud;
}
}
......@@ -4,13 +4,19 @@
*--------------------------------------------------------------------------------------------*/
import './media/agentsessionsactions.css';
import { localize } from '../../../../../nls.js';
import { localize, localize2 } from '../../../../../nls.js';
import { IAgentSessionViewModel } from './agentSessionViewModel.js';
import { Action, IAction } from '../../../../../base/common/actions.js';
import { ActionViewItem, IActionViewItemOptions } from '../../../../../base/browser/ui/actionbar/actionViewItems.js';
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
import { EventHelper, h, hide, show } from '../../../../../base/browser/dom.js';
import { assertReturnsDefined } from '../../../../../base/common/types.js';
import { ISubmenuItem, MenuId, MenuRegistry, registerAction2 } from '../../../../../platform/actions/common/actions.js';
import { Codicon } from '../../../../../base/common/codicons.js';
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
import { ViewAction } from '../../../../browser/parts/views/viewPane.js';
import { AGENT_SESSIONS_VIEW_ID } from './agentSessions.js';
import { AgentSessionsView } from './agentSessionsView.js';
//#region Diff Statistics Action
......@@ -102,3 +108,53 @@ export class AgentSessionDiffActionViewItem extends ActionViewItem {
}
//#endregion
//#region View Actions
registerAction2(class extends ViewAction<AgentSessionsView> {
constructor() {
super({
id: 'agentSessionsView.refresh',
title: localize2('refresh', "Refresh Agent Sessions"),
icon: Codicon.refresh,
menu: {
id: MenuId.AgentSessionsTitle,
group: 'navigation',
order: 1
},
viewId: AGENT_SESSIONS_VIEW_ID
});
}
runInView(accessor: ServicesAccessor, view: AgentSessionsView): void {
view.refresh();
}
});
registerAction2(class extends ViewAction<AgentSessionsView> {
constructor() {
super({
id: 'agentSessionsView.find',
title: localize2('find', "Find Agent Session"),
icon: Codicon.search,
menu: {
id: MenuId.AgentSessionsTitle,
group: 'navigation',
order: 2
},
viewId: AGENT_SESSIONS_VIEW_ID
});
}
runInView(accessor: ServicesAccessor, view: AgentSessionsView): void {
view.openFind();
}
});
MenuRegistry.appendMenuItem(MenuId.AgentSessionsTitle, {
submenu: MenuId.AgentSessionsFilterSubMenu,
title: localize('filterAgentSessions', "Filter Agent Sessions"),
group: 'navigation',
order: 100,
icon: Codicon.filter
} satisfies ISubmenuItem);
//#endregion
......@@ -10,7 +10,7 @@ import { ContextKeyExpr, IContextKeyService } from '../../../../../platform/cont
import { SyncDescriptor } from '../../../../../platform/instantiation/common/descriptors.js';
import { Registry } from '../../../../../platform/registry/common/platform.js';
import { registerIcon } from '../../../../../platform/theme/common/iconRegistry.js';
import { IViewPaneOptions, ViewAction, ViewPane } from '../../../../browser/parts/views/viewPane.js';
import { IViewPaneOptions, ViewPane } from '../../../../browser/parts/views/viewPane.js';
import { ViewPaneContainer } from '../../../../browser/parts/views/viewPaneContainer.js';
import { IViewContainersRegistry, Extensions as ViewExtensions, ViewContainerLocation, IViewsRegistry, IViewDescriptor, IViewDescriptorService } from '../../../../common/views.js';
import { ChatContextKeys } from '../../common/chatContextKeys.js';
......@@ -18,7 +18,7 @@ import { ChatConfiguration } from '../../common/constants.js';
import { IConfigurationService } from '../../../../../platform/configuration/common/configuration.js';
import { IContextMenuService } from '../../../../../platform/contextview/browser/contextView.js';
import { IHoverService } from '../../../../../platform/hover/browser/hover.js';
import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js';
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
import { IKeybindingService } from '../../../../../platform/keybinding/common/keybinding.js';
import { IOpenerService } from '../../../../../platform/opener/common/opener.js';
import { IThemeService } from '../../../../../platform/theme/common/themeService.js';
......@@ -30,7 +30,7 @@ import { defaultButtonStyles } from '../../../../../platform/theme/browser/defau
import { ButtonWithDropdown } from '../../../../../base/browser/ui/button/button.js';
import { IAction, Separator, toAction } from '../../../../../base/common/actions.js';
import { FuzzyScore } from '../../../../../base/common/filters.js';
import { IMenuService, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js';
import { IMenuService, MenuId } from '../../../../../platform/actions/common/actions.js';
import { IChatSessionsService } from '../../common/chatSessionsService.js';
import { ICommandService } from '../../../../../platform/commands/common/commands.js';
import { getSessionItemContextOverlay, NEW_CHAT_SESSION_ACTION_ID } from '../chatSessions/common.js';
......@@ -74,9 +74,7 @@ export class AgentSessionsView extends ViewPane {
@IMenuService private readonly menuService: IMenuService,
@IChatWidgetService private readonly chatWidgetService: IChatWidgetService,
) {
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
this.registerActions();
super({ ...options, titleMenuId: MenuId.AgentSessionsTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
}
protected override renderBody(container: HTMLElement): void {
......@@ -94,9 +92,9 @@ export class AgentSessionsView extends ViewPane {
}
private registerListeners(): void {
const list = assertReturnsDefined(this.list);
// Sessions List
const list = assertReturnsDefined(this.list);
this._register(this.onDidChangeBodyVisibility(visible => {
if (!visible || this.sessionsViewModel) {
return;
......@@ -124,7 +122,7 @@ export class AgentSessionsView extends ViewPane {
}));
}
private async openAgentSession(e: IOpenEvent<IAgentSessionViewModel | undefined>) {
private async openAgentSession(e: IOpenEvent<IAgentSessionViewModel | undefined>): Promise<void> {
const session = e.element;
if (!session) {
return;
......@@ -172,48 +170,7 @@ export class AgentSessionsView extends ViewPane {
menu.dispose();
}
private registerActions(): void {
this._register(registerAction2(class extends ViewAction<AgentSessionsView> {
constructor() {
super({
id: 'agentSessionsView.refresh',
title: localize2('refresh', "Refresh Agent Sessions"),
icon: Codicon.refresh,
menu: {
id: MenuId.ViewTitle,
when: ContextKeyExpr.equals('view', AGENT_SESSIONS_VIEW_ID),
group: 'navigation',
order: 1
},
viewId: AGENT_SESSIONS_VIEW_ID
});
}
runInView(accessor: ServicesAccessor, view: AgentSessionsView): void {
view.sessionsViewModel?.resolve(undefined);
}
}));
this._register(registerAction2(class extends ViewAction<AgentSessionsView> {
constructor() {
super({
id: 'agentSessionsView.find',
title: localize2('find', "Find Agent Session"),
icon: Codicon.search,
menu: {
id: MenuId.ViewTitle,
when: ContextKeyExpr.equals('view', AGENT_SESSIONS_VIEW_ID),
group: 'navigation',
order: 2
},
viewId: AGENT_SESSIONS_VIEW_ID
});
}
runInView(accessor: ServicesAccessor, view: AgentSessionsView): void {
view.list?.openFind();
}
}));
}
//#endregion
//#region New Session Controls
......@@ -343,7 +300,7 @@ export class AgentSessionsView extends ViewPane {
}
private createViewModel(): void {
const sessionsViewModel = this.sessionsViewModel = this._register(this.instantiationService.createInstance(AgentSessionsViewModel));
const sessionsViewModel = this.sessionsViewModel = this._register(this.instantiationService.createInstance(AgentSessionsViewModel, { filterMenuId: MenuId.AgentSessionsFilterSubMenu }));
this.list?.setInput(sessionsViewModel);
this._register(sessionsViewModel.onDidChangeSessions(() => {
......@@ -370,6 +327,18 @@ export class AgentSessionsView extends ViewPane {
//#endregion
//#region Actions internal API
openFind(): void {
this.list?.openFind();
}
refresh(): void {
this.sessionsViewModel?.resolve(undefined);
}
//#endregion
protected override layoutBody(height: number, width: number): void {
super.layoutBody(height, width);
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter } from '../../../../../base/common/event.js';
import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js';
import { escapeRegExpCharacters } from '../../../../../base/common/strings.js';
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
import { registerAction2, Action2, MenuId } from '../../../../../platform/actions/common/actions.js';
import { ContextKeyExpr, IContextKey, IContextKeyService, RawContextKey } from '../../../../../platform/contextkey/common/contextkey.js';
import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';
import { IChatSessionsService } from '../../common/chatSessionsService.js';
import { AgentSessionProviders, getAgentSessionProviderName } from './agentSessions.js';
export interface IAgentSessionsViewFilterOptions {
readonly filterMenuId: MenuId;
}
export class AgentSessionsViewFilter extends Disposable {
private static readonly STORAGE_KEY = 'agentSessions.filter.excludes';
private static readonly CONTEXT_KEY = 'agentSessionsFilterExcludes';
private readonly _onDidChange = this._register(new Emitter<void>());
readonly onDidChange = this._onDidChange.event;
private _excludes = new Set<string>();
get excludes(): Set<string> { return this._excludes; }
private excludesContext: IContextKey<string>;
private actionDisposables = this._register(new DisposableStore());
constructor(
private readonly options: IAgentSessionsViewFilterOptions,
@IChatSessionsService private readonly chatSessionsService: IChatSessionsService,
@IStorageService private readonly storageService: IStorageService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
) {
super();
this.excludesContext = new RawContextKey<string>(AgentSessionsViewFilter.CONTEXT_KEY, '[]', true).bindTo(this.contextKeyService);
this.updateExcludes(false);
this.registerListeners();
}
private registerListeners(): void {
this._register(this.chatSessionsService.onDidChangeItemsProviders(() => this.updateFilterActions()));
this._register(this.chatSessionsService.onDidChangeAvailability(() => this.updateFilterActions()));
this._register(this.storageService.onDidChangeValue(StorageScope.PROFILE, AgentSessionsViewFilter.STORAGE_KEY, this._store)(() => this.updateExcludes(true)));
}
private updateExcludes(fromEvent: boolean): void {
const excludedTypesString = this.storageService.get(AgentSessionsViewFilter.STORAGE_KEY, StorageScope.PROFILE, '[]');
this.excludesContext.set(excludedTypesString);
this._excludes = new Set(JSON.parse(excludedTypesString));
if (fromEvent) {
this._onDidChange.fire();
}
}
private updateFilterActions(): void {
this.actionDisposables.clear();
const providers: { id: string; label: string }[] = [
{ id: AgentSessionProviders.Local, label: getAgentSessionProviderName(AgentSessionProviders.Local) },
{ id: AgentSessionProviders.Background, label: getAgentSessionProviderName(AgentSessionProviders.Background) },
{ id: AgentSessionProviders.Cloud, label: getAgentSessionProviderName(AgentSessionProviders.Cloud) },
];
for (const provider of this.chatSessionsService.getAllChatSessionContributions()) {
if (providers.find(p => p.id === provider.type)) {
continue; // already added
}
providers.push({ id: provider.type, label: provider.name });
}
const that = this;
let counter = 0;
for (const provider of providers) {
this.actionDisposables.add(registerAction2(class extends Action2 {
constructor() {
super({
id: `agentSessions.filter.toggleExclude:${provider.id}`,
title: provider.label,
menu: {
id: that.options.filterMenuId,
group: 'navigation',
order: counter++,
},
toggled: ContextKeyExpr.regex(AgentSessionsViewFilter.CONTEXT_KEY, new RegExp(`\\b${escapeRegExpCharacters(provider.id)}\\b`)).negate()
});
}
run(accessor: ServicesAccessor): void {
const excludes = new Set(that._excludes);
if (excludes.has(provider.id)) {
excludes.delete(provider.id);
} else {
excludes.add(provider.id);
}
const storageService = accessor.get(IStorageService);
storageService.store(AgentSessionsViewFilter.STORAGE_KEY, JSON.stringify([...excludes]), StorageScope.PROFILE, StorageTarget.USER);
}
}));
}
}
}
......@@ -11,12 +11,15 @@ import { ThemeIcon } from '../../../../../base/common/themables.js';
import { URI } from '../../../../../base/common/uri.js';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
import { AgentSessionsViewModel, IAgentSessionViewModel, isAgentSession, isAgentSessionsViewModel, isLocalAgentSessionItem } from '../../browser/agentSessions/agentSessionViewModel.js';
import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, localChatSessionType } from '../../common/chatSessionsService.js';
import { ChatSessionStatus, IChatSessionItem, IChatSessionItemProvider, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js';
import { LocalChatSessionUri } from '../../common/chatUri.js';
import { MockChatSessionsService } from '../common/mockChatSessionsService.js';
import { TestLifecycleService } from '../../../../test/browser/workbenchTestServices.js';
import { TestLifecycleService, workbenchInstantiationService } from '../../../../test/browser/workbenchTestServices.js';
import { runWithFakedTimers } from '../../../../../base/test/common/timeTravelScheduler.js';
import { Codicon } from '../../../../../base/common/codicons.js';
import { MenuId } from '../../../../../platform/actions/common/actions.js';
import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js';
import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
suite('AgentSessionsViewModel', () => {
......@@ -24,10 +27,21 @@ suite('AgentSessionsViewModel', () => {
let mockChatSessionsService: MockChatSessionsService;
let mockLifecycleService: TestLifecycleService;
let viewModel: AgentSessionsViewModel;
let instantiationService: TestInstantiationService;
function createViewModel(): AgentSessionsViewModel {
return disposables.add(instantiationService.createInstance(
AgentSessionsViewModel,
{ filterMenuId: MenuId.ViewTitle }
));
}
setup(() => {
mockChatSessionsService = new MockChatSessionsService();
mockLifecycleService = disposables.add(new TestLifecycleService());
instantiationService = disposables.add(workbenchInstantiationService(undefined, disposables));
instantiationService.stub(IChatSessionsService, mockChatSessionsService);
instantiationService.stub(ILifecycleService, mockLifecycleService);
});
teardown(() => {
......@@ -37,10 +51,7 @@ suite('AgentSessionsViewModel', () => {
ensureNoDisposablesAreLeakedInTestSuite();
test('should initialize with empty sessions', () => {
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
assert.strictEqual(viewModel.sessions.length, 0);
});
......@@ -66,10 +77,7 @@ suite('AgentSessionsViewModel', () => {
};
mockChatSessionsService.registerChatSessionItemProvider(provider);
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
await viewModel.resolve(undefined);
......@@ -110,10 +118,7 @@ suite('AgentSessionsViewModel', () => {
mockChatSessionsService.registerChatSessionItemProvider(provider1);
mockChatSessionsService.registerChatSessionItemProvider(provider2);
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
await viewModel.resolve(undefined);
......@@ -132,10 +137,7 @@ suite('AgentSessionsViewModel', () => {
};
mockChatSessionsService.registerChatSessionItemProvider(provider);
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
let willResolveFired = false;
let didResolveFired = false;
......@@ -172,10 +174,7 @@ suite('AgentSessionsViewModel', () => {
};
mockChatSessionsService.registerChatSessionItemProvider(provider);
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
let sessionsChangedFired = false;
disposables.add(viewModel.onDidChangeSessions(() => {
......@@ -211,10 +210,7 @@ suite('AgentSessionsViewModel', () => {
};
mockChatSessionsService.registerChatSessionItemProvider(provider);
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
await viewModel.resolve(undefined);
......@@ -248,10 +244,7 @@ suite('AgentSessionsViewModel', () => {
};
mockChatSessionsService.registerChatSessionItemProvider(provider);
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
await viewModel.resolve(undefined);
......@@ -290,10 +283,7 @@ suite('AgentSessionsViewModel', () => {
mockChatSessionsService.registerChatSessionItemProvider(provider1);
mockChatSessionsService.registerChatSessionItemProvider(provider2);
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
// First resolve all
await viewModel.resolve(undefined);
......@@ -336,10 +326,7 @@ suite('AgentSessionsViewModel', () => {
mockChatSessionsService.registerChatSessionItemProvider(provider1);
mockChatSessionsService.registerChatSessionItemProvider(provider2);
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
await viewModel.resolve(['type-1', 'type-2']);
......@@ -362,10 +349,7 @@ suite('AgentSessionsViewModel', () => {
};
mockChatSessionsService.registerChatSessionItemProvider(provider);
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
const sessionsChangedPromise = Event.toPromise(viewModel.onDidChangeSessions);
......@@ -394,10 +378,7 @@ suite('AgentSessionsViewModel', () => {
};
mockChatSessionsService.registerChatSessionItemProvider(provider);
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
const sessionsChangedPromise = Event.toPromise(viewModel.onDidChangeSessions);
......@@ -426,10 +407,7 @@ suite('AgentSessionsViewModel', () => {
};
mockChatSessionsService.registerChatSessionItemProvider(provider);
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
const sessionsChangedPromise = Event.toPromise(viewModel.onDidChangeSessions);
......@@ -458,10 +436,7 @@ suite('AgentSessionsViewModel', () => {
};
mockChatSessionsService.registerChatSessionItemProvider(provider);
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
await viewModel.resolve(undefined);
......@@ -480,10 +455,7 @@ suite('AgentSessionsViewModel', () => {
};
mockChatSessionsService.registerChatSessionItemProvider(provider);
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
await viewModel.resolve(undefined);
......@@ -522,10 +494,7 @@ suite('AgentSessionsViewModel', () => {
};
mockChatSessionsService.registerChatSessionItemProvider(provider);
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
await viewModel.resolve(undefined);
......@@ -557,10 +526,7 @@ suite('AgentSessionsViewModel', () => {
};
mockChatSessionsService.registerChatSessionItemProvider(provider);
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
await viewModel.resolve(undefined);
assert.strictEqual(viewModel.sessions.length, 1);
......@@ -587,10 +553,7 @@ suite('AgentSessionsViewModel', () => {
};
mockChatSessionsService.registerChatSessionItemProvider(provider);
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
await viewModel.resolve(undefined);
......@@ -616,10 +579,7 @@ suite('AgentSessionsViewModel', () => {
};
mockChatSessionsService.registerChatSessionItemProvider(provider);
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
await viewModel.resolve(undefined);
......@@ -648,10 +608,7 @@ suite('AgentSessionsViewModel', () => {
};
mockChatSessionsService.registerChatSessionItemProvider(provider);
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
// Make multiple rapid resolve calls
const resolvePromises = [
......@@ -706,10 +663,7 @@ suite('AgentSessionsViewModel', () => {
mockChatSessionsService.registerChatSessionItemProvider(provider1);
mockChatSessionsService.registerChatSessionItemProvider(provider2);
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
// First resolve all
await viewModel.resolve(undefined);
......@@ -768,10 +722,7 @@ suite('AgentSessionsViewModel', () => {
mockChatSessionsService.registerChatSessionItemProvider(provider1);
mockChatSessionsService.registerChatSessionItemProvider(provider2);
viewModel = disposables.add(new AgentSessionsViewModel(
mockChatSessionsService,
mockLifecycleService
));
viewModel = createViewModel();
// Call resolve with different types rapidly - they should accumulate
const promise1 = viewModel.resolve('type-1');
......@@ -866,8 +817,14 @@ suite('AgentSessionsViewModel - Helper Functions', () => {
};
// Test with actual view model
const actualViewModel = new AgentSessionsViewModel(new MockChatSessionsService(), disposables.add(new TestLifecycleService()));
disposables.add(actualViewModel);
const instantiationService = workbenchInstantiationService(undefined, disposables);
const lifecycleService = disposables.add(new TestLifecycleService());
instantiationService.stub(IChatSessionsService, new MockChatSessionsService());
instantiationService.stub(ILifecycleService, lifecycleService);
const actualViewModel = disposables.add(instantiationService.createInstance(
AgentSessionsViewModel,
{ filterMenuId: MenuId.ViewTitle }
));
assert.strictEqual(isAgentSessionsViewModel(actualViewModel), true);
// Test with session object
......
Поддерживает Markdown
0% или .
You are about to add 0 people to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Пожалуйста, зарегистрируйтесь или чтобы прокомментировать