mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-26 21:28:04 +00:00
Merge pull request #279024 from microsoft/ben/little-lizard
agent sessions - refactoring and services
This commit is contained in:
@@ -225,6 +225,7 @@ export class MenuId {
|
||||
static readonly TimelineFilterSubMenu = new MenuId('TimelineFilterSubMenu');
|
||||
static readonly AgentSessionsTitle = new MenuId('AgentSessionsTitle');
|
||||
static readonly AgentSessionsFilterSubMenu = new MenuId('AgentSessionsFilterSubMenu');
|
||||
static readonly AgentSessionItemToolbar = new MenuId('AgentSessionItemToolbar');
|
||||
static readonly AccountsContext = new MenuId('AccountsContext');
|
||||
static readonly SidebarTitle = new MenuId('SidebarTitle');
|
||||
static readonly PanelTitle = new MenuId('PanelTitle');
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import './media/agentsessionsactions.css';
|
||||
import { localize, localize2 } from '../../../../../nls.js';
|
||||
import { IAgentSessionViewModel } from './agentSessionViewModel.js';
|
||||
import { IAgentSession } from './agentSessionsModel.js';
|
||||
import { Action, IAction } from '../../../../../base/common/actions.js';
|
||||
import { ActionViewItem, IActionViewItemOptions } from '../../../../../base/browser/ui/actionbar/actionViewItems.js';
|
||||
import { CommandsRegistry, ICommandService } from '../../../../../platform/commands/common/commands.js';
|
||||
@@ -19,10 +19,8 @@ import { AGENT_SESSIONS_VIEW_ID, AgentSessionProviders } from './agentSessions.j
|
||||
import { AgentSessionsView } from './agentSessionsView.js';
|
||||
import { URI } from '../../../../../base/common/uri.js';
|
||||
import { IChatService } from '../../common/chatService.js';
|
||||
import { IStorageService } from '../../../../../platform/storage/common/storage.js';
|
||||
import { resetFilter } from './agentSessionsViewFilter.js';
|
||||
import { CHAT_CATEGORY } from '../actions/chatActions.js';
|
||||
import { ChatContextKeys } from '../../common/chatContextKeys.js';
|
||||
import { CHAT_CATEGORY } from '../actions/chatActions.js';
|
||||
import { NEW_CHAT_SESSION_ACTION_ID } from '../chatSessions/common.js';
|
||||
|
||||
//#region New Chat Session Actions
|
||||
@@ -73,14 +71,56 @@ registerAction2(class NewCloudChatAction extends Action2 {
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Diff Statistics Action
|
||||
//#region Item Title Actions
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'agentSession.archive',
|
||||
title: localize('archive', "Archive"),
|
||||
icon: Codicon.archive,
|
||||
menu: {
|
||||
id: MenuId.AgentSessionItemToolbar,
|
||||
group: 'navigation',
|
||||
order: 1,
|
||||
when: ChatContextKeys.isArchivedItem.negate(),
|
||||
}
|
||||
});
|
||||
}
|
||||
run(accessor: ServicesAccessor, session: IAgentSession): void {
|
||||
session.setArchived(true);
|
||||
}
|
||||
});
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'agentSession.unarchive',
|
||||
title: localize('unarchive', "Unarchive"),
|
||||
icon: Codicon.discard,
|
||||
menu: {
|
||||
id: MenuId.AgentSessionItemToolbar,
|
||||
group: 'navigation',
|
||||
order: 1,
|
||||
when: ChatContextKeys.isArchivedItem,
|
||||
}
|
||||
});
|
||||
}
|
||||
run(accessor: ServicesAccessor, session: IAgentSession): void {
|
||||
session.setArchived(false);
|
||||
}
|
||||
});
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Item Detail Actions
|
||||
|
||||
export class AgentSessionShowDiffAction extends Action {
|
||||
|
||||
static ID = 'agentSession.showDiff';
|
||||
|
||||
constructor(
|
||||
private readonly session: IAgentSessionViewModel
|
||||
private readonly session: IAgentSession
|
||||
) {
|
||||
super(AgentSessionShowDiffAction.ID, localize('showDiff', "Open Changes"), undefined, true);
|
||||
}
|
||||
@@ -89,7 +129,7 @@ export class AgentSessionShowDiffAction extends Action {
|
||||
// This will be handled by the action view item
|
||||
}
|
||||
|
||||
getSession(): IAgentSessionViewModel {
|
||||
getSession(): IAgentSession {
|
||||
return this.session;
|
||||
}
|
||||
}
|
||||
@@ -219,23 +259,4 @@ MenuRegistry.appendMenuItem(MenuId.AgentSessionsTitle, {
|
||||
icon: Codicon.filter
|
||||
} satisfies ISubmenuItem);
|
||||
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'agentSessions.filter.resetExcludes',
|
||||
title: localize('agentSessions.filter.reset', 'Reset'),
|
||||
menu: {
|
||||
id: MenuId.AgentSessionsFilterSubMenu,
|
||||
group: '4_reset',
|
||||
order: 0,
|
||||
},
|
||||
});
|
||||
}
|
||||
run(accessor: ServicesAccessor): void {
|
||||
const storageService = accessor.get(IStorageService);
|
||||
|
||||
resetFilter(storageService);
|
||||
}
|
||||
});
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -11,9 +11,9 @@ import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contex
|
||||
import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';
|
||||
import { ChatSessionStatus, IChatSessionsService } from '../../common/chatSessionsService.js';
|
||||
import { AgentSessionProviders, getAgentSessionProviderName } from './agentSessions.js';
|
||||
import { IAgentSessionViewModel } from './agentSessionViewModel.js';
|
||||
import { IAgentSession } from './agentSessionsModel.js';
|
||||
|
||||
export interface IAgentSessionsViewFilterOptions {
|
||||
export interface IAgentSessionsFilterOptions {
|
||||
readonly filterMenuId: MenuId;
|
||||
}
|
||||
|
||||
@@ -29,21 +29,9 @@ const DEFAULT_EXCLUDES: IAgentSessionsViewExcludes = Object.freeze({
|
||||
archived: true as const,
|
||||
});
|
||||
|
||||
const FILTER_STORAGE_KEY = 'agentSessions.filterExcludes';
|
||||
export class AgentSessionsFilter extends Disposable {
|
||||
|
||||
export function resetFilter(storageService: IStorageService): void {
|
||||
const excludes = {
|
||||
providers: [...DEFAULT_EXCLUDES.providers],
|
||||
states: [...DEFAULT_EXCLUDES.states],
|
||||
archived: DEFAULT_EXCLUDES.archived,
|
||||
};
|
||||
|
||||
storageService.store(FILTER_STORAGE_KEY, JSON.stringify(excludes), StorageScope.PROFILE, StorageTarget.USER);
|
||||
}
|
||||
|
||||
export class AgentSessionsViewFilter extends Disposable {
|
||||
|
||||
private static readonly STORAGE_KEY = FILTER_STORAGE_KEY;
|
||||
private readonly STORAGE_KEY: string;
|
||||
|
||||
private readonly _onDidChange = this._register(new Emitter<void>());
|
||||
readonly onDidChange = this._onDidChange.event;
|
||||
@@ -53,12 +41,14 @@ export class AgentSessionsViewFilter extends Disposable {
|
||||
private actionDisposables = this._register(new DisposableStore());
|
||||
|
||||
constructor(
|
||||
private readonly options: IAgentSessionsViewFilterOptions,
|
||||
private readonly options: IAgentSessionsFilterOptions,
|
||||
@IChatSessionsService private readonly chatSessionsService: IChatSessionsService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this.STORAGE_KEY = `agentSessions.filterExcludes.${this.options.filterMenuId.id.toLowerCase()}`;
|
||||
|
||||
this.updateExcludes(false);
|
||||
|
||||
this.registerListeners();
|
||||
@@ -68,16 +58,20 @@ export class AgentSessionsViewFilter extends Disposable {
|
||||
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)));
|
||||
this._register(this.storageService.onDidChangeValue(StorageScope.PROFILE, this.STORAGE_KEY, this._store)(() => this.updateExcludes(true)));
|
||||
}
|
||||
|
||||
private updateExcludes(fromEvent: boolean): void {
|
||||
const excludedTypesRaw = this.storageService.get(AgentSessionsViewFilter.STORAGE_KEY, StorageScope.PROFILE);
|
||||
this.excludes = excludedTypesRaw ? JSON.parse(excludedTypesRaw) as IAgentSessionsViewExcludes : {
|
||||
providers: [...DEFAULT_EXCLUDES.providers],
|
||||
states: [...DEFAULT_EXCLUDES.states],
|
||||
archived: DEFAULT_EXCLUDES.archived,
|
||||
};
|
||||
const excludedTypesRaw = this.storageService.get(this.STORAGE_KEY, StorageScope.PROFILE);
|
||||
if (excludedTypesRaw) {
|
||||
try {
|
||||
this.excludes = JSON.parse(excludedTypesRaw) as IAgentSessionsViewExcludes;
|
||||
} catch {
|
||||
this.resetExcludes();
|
||||
}
|
||||
} else {
|
||||
this.resetExcludes();
|
||||
}
|
||||
|
||||
this.updateFilterActions();
|
||||
|
||||
@@ -86,10 +80,18 @@ export class AgentSessionsViewFilter extends Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private resetExcludes(): void {
|
||||
this.excludes = {
|
||||
providers: [...DEFAULT_EXCLUDES.providers],
|
||||
states: [...DEFAULT_EXCLUDES.states],
|
||||
archived: DEFAULT_EXCLUDES.archived,
|
||||
};
|
||||
}
|
||||
|
||||
private storeExcludes(excludes: IAgentSessionsViewExcludes): void {
|
||||
this.excludes = excludes;
|
||||
|
||||
this.storageService.store(AgentSessionsViewFilter.STORAGE_KEY, JSON.stringify(this.excludes), StorageScope.PROFILE, StorageTarget.USER);
|
||||
this.storageService.store(this.STORAGE_KEY, JSON.stringify(this.excludes), StorageScope.PROFILE, StorageTarget.USER);
|
||||
}
|
||||
|
||||
private updateFilterActions(): void {
|
||||
@@ -98,6 +100,7 @@ export class AgentSessionsViewFilter extends Disposable {
|
||||
this.registerProviderActions(this.actionDisposables);
|
||||
this.registerStateActions(this.actionDisposables);
|
||||
this.registerArchivedActions(this.actionDisposables);
|
||||
this.registerResetAction(this.actionDisposables);
|
||||
}
|
||||
|
||||
private registerProviderActions(disposables: DisposableStore): void {
|
||||
@@ -121,7 +124,7 @@ export class AgentSessionsViewFilter extends Disposable {
|
||||
disposables.add(registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `agentSessions.filter.toggleExclude:${provider.id}`,
|
||||
id: `agentSessions.filter.toggleExclude:${provider.id}.${that.options.filterMenuId.id.toLowerCase()}`,
|
||||
title: provider.label,
|
||||
menu: {
|
||||
id: that.options.filterMenuId,
|
||||
@@ -156,7 +159,7 @@ export class AgentSessionsViewFilter extends Disposable {
|
||||
disposables.add(registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `agentSessions.filter.toggleExcludeState:${state.id}`,
|
||||
id: `agentSessions.filter.toggleExcludeState:${state.id}.${that.options.filterMenuId.id.toLowerCase()}`,
|
||||
title: state.label,
|
||||
menu: {
|
||||
id: that.options.filterMenuId,
|
||||
@@ -183,7 +186,7 @@ export class AgentSessionsViewFilter extends Disposable {
|
||||
disposables.add(registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'agentSessions.filter.toggleExcludeArchived',
|
||||
id: `agentSessions.filter.toggleExcludeArchived.${that.options.filterMenuId.id.toLowerCase()}`,
|
||||
title: localize('agentSessions.filter.archived', 'Archived'),
|
||||
menu: {
|
||||
id: that.options.filterMenuId,
|
||||
@@ -199,8 +202,30 @@ export class AgentSessionsViewFilter extends Disposable {
|
||||
}));
|
||||
}
|
||||
|
||||
exclude(session: IAgentSessionViewModel): boolean {
|
||||
if (this.excludes.archived && session.archived) {
|
||||
private registerResetAction(disposables: DisposableStore): void {
|
||||
const that = this;
|
||||
disposables.add(registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `agentSessions.filter.resetExcludes.${that.options.filterMenuId.id.toLowerCase()}`,
|
||||
title: localize('agentSessions.filter.reset', "Reset"),
|
||||
menu: {
|
||||
id: MenuId.AgentSessionsFilterSubMenu,
|
||||
group: '4_reset',
|
||||
order: 0,
|
||||
},
|
||||
});
|
||||
}
|
||||
run(): void {
|
||||
that.resetExcludes();
|
||||
|
||||
that.storageService.store(that.STORAGE_KEY, JSON.stringify(that.excludes), StorageScope.PROFILE, StorageTarget.USER);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
exclude(session: IAgentSession): boolean {
|
||||
if (this.excludes.archived && session.isArchived()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -12,29 +12,27 @@ import { Disposable } from '../../../../../base/common/lifecycle.js';
|
||||
import { ResourceMap } from '../../../../../base/common/map.js';
|
||||
import { ThemeIcon } from '../../../../../base/common/themables.js';
|
||||
import { URI, UriComponents } from '../../../../../base/common/uri.js';
|
||||
import { MenuId } from '../../../../../platform/actions/common/actions.js';
|
||||
import { IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
|
||||
import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';
|
||||
import { ILifecycleService } from '../../../../services/lifecycle/common/lifecycle.js';
|
||||
import { ChatSessionStatus, IChatSessionsExtensionPoint, IChatSessionsService, localChatSessionType } from '../../common/chatSessionsService.js';
|
||||
import { AgentSessionProviders, getAgentSessionProviderIcon, getAgentSessionProviderName } from './agentSessions.js';
|
||||
import { AgentSessionsViewFilter } from './agentSessionsViewFilter.js';
|
||||
|
||||
//#region Interfaces, Types
|
||||
|
||||
export interface IAgentSessionsViewModel {
|
||||
export interface IAgentSessionsModel {
|
||||
|
||||
readonly onWillResolve: Event<void>;
|
||||
readonly onDidResolve: Event<void>;
|
||||
|
||||
readonly onDidChangeSessions: Event<void>;
|
||||
|
||||
readonly sessions: IAgentSessionViewModel[];
|
||||
readonly sessions: IAgentSession[];
|
||||
|
||||
resolve(provider: string | string[] | undefined): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IAgentSessionViewModel {
|
||||
interface IAgentSessionData {
|
||||
|
||||
readonly providerType: string;
|
||||
readonly providerLabel: string;
|
||||
@@ -42,7 +40,6 @@ export interface IAgentSessionViewModel {
|
||||
readonly resource: URI;
|
||||
|
||||
readonly status: ChatSessionStatus;
|
||||
readonly archived: boolean;
|
||||
|
||||
readonly tooltip?: string | IMarkdownString;
|
||||
|
||||
@@ -65,29 +62,44 @@ export interface IAgentSessionViewModel {
|
||||
};
|
||||
}
|
||||
|
||||
export function isLocalAgentSessionItem(session: IAgentSessionViewModel): boolean {
|
||||
export interface IAgentSession extends IAgentSessionData {
|
||||
isArchived(): boolean;
|
||||
setArchived(archived: boolean): void;
|
||||
}
|
||||
|
||||
interface IInternalAgentSessionData extends IAgentSessionData {
|
||||
|
||||
/**
|
||||
* The `archived` property is provided by the session provider
|
||||
* and will be used as the initial value if the user has not
|
||||
* changed the archived state for the session previously. It
|
||||
* is kept internal to not expose it publicly. Use `isArchived()`
|
||||
* and `setArchived()` methods instead.
|
||||
*/
|
||||
readonly archived: boolean | undefined;
|
||||
}
|
||||
|
||||
interface IInternalAgentSession extends IAgentSession, IInternalAgentSessionData { }
|
||||
|
||||
export function isLocalAgentSessionItem(session: IAgentSession): boolean {
|
||||
return session.providerType === localChatSessionType;
|
||||
}
|
||||
|
||||
export function isAgentSession(obj: IAgentSessionsViewModel | IAgentSessionViewModel): obj is IAgentSessionViewModel {
|
||||
const session = obj as IAgentSessionViewModel | undefined;
|
||||
export function isAgentSession(obj: IAgentSessionsModel | IAgentSession): obj is IAgentSession {
|
||||
const session = obj as IAgentSession | undefined;
|
||||
|
||||
return URI.isUri(session?.resource);
|
||||
}
|
||||
|
||||
export function isAgentSessionsViewModel(obj: IAgentSessionsViewModel | IAgentSessionViewModel): obj is IAgentSessionsViewModel {
|
||||
const sessionsViewModel = obj as IAgentSessionsViewModel | undefined;
|
||||
export function isAgentSessionsModel(obj: IAgentSessionsModel | IAgentSession): obj is IAgentSessionsModel {
|
||||
const sessionsModel = obj as IAgentSessionsModel | undefined;
|
||||
|
||||
return Array.isArray(sessionsViewModel?.sessions);
|
||||
return Array.isArray(sessionsModel?.sessions);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
export interface IAgentSessionsViewModelOptions {
|
||||
readonly filterMenuId: MenuId;
|
||||
}
|
||||
|
||||
export class AgentSessionsViewModel extends Disposable implements IAgentSessionsViewModel {
|
||||
export class AgentSessionsModel extends Disposable implements IAgentSessionsModel {
|
||||
|
||||
private readonly _onWillResolve = this._register(new Emitter<void>());
|
||||
readonly onWillResolve = this._onWillResolve.event;
|
||||
@@ -98,11 +110,8 @@ 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.exclude(session));
|
||||
}
|
||||
private _sessions: IInternalAgentSession[] = [];
|
||||
get sessions(): IAgentSession[] { return this._sessions; }
|
||||
|
||||
private readonly resolver = this._register(new ThrottledDelayer<void>(100));
|
||||
private readonly providersToResolve = new Set<string | undefined>();
|
||||
@@ -114,11 +123,9 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
|
||||
finishedOrFailedTime?: number;
|
||||
}>();
|
||||
|
||||
private readonly filter: AgentSessionsViewFilter;
|
||||
private readonly cache: AgentSessionsCache;
|
||||
|
||||
constructor(
|
||||
options: IAgentSessionsViewModelOptions,
|
||||
@IChatSessionsService private readonly chatSessionsService: IChatSessionsService,
|
||||
@ILifecycleService private readonly lifecycleService: ILifecycleService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@@ -126,12 +133,9 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
|
||||
) {
|
||||
super();
|
||||
|
||||
this.filter = this._register(this.instantiationService.createInstance(AgentSessionsViewFilter, { filterMenuId: options.filterMenuId }));
|
||||
|
||||
this.cache = this.instantiationService.createInstance(AgentSessionsCache);
|
||||
this._sessions = this.cache.loadCachedSessions();
|
||||
|
||||
this.resolve(undefined);
|
||||
this._sessions = this.cache.loadCachedSessions().map(data => this.toAgentSession(data));
|
||||
this.sessionStates = this.cache.loadSessionStates();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
@@ -140,8 +144,10 @@ 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()));
|
||||
this._register(this.storageService.onWillSaveState(() => this.cache.saveCachedSessions(this._sessions)));
|
||||
this._register(this.storageService.onWillSaveState(() => {
|
||||
this.cache.saveCachedSessions(this._sessions);
|
||||
this.cache.saveSessionStates(this.sessionStates);
|
||||
}));
|
||||
}
|
||||
|
||||
async resolve(provider: string | string[] | undefined): Promise<void> {
|
||||
@@ -177,7 +183,7 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
|
||||
}
|
||||
|
||||
const resolvedProviders = new Set<string>();
|
||||
const sessions = new ResourceMap<IAgentSessionViewModel>();
|
||||
const sessions = new ResourceMap<IInternalAgentSession>();
|
||||
for (const provider of this.chatSessionsService.getAllChatSessionItemProviders()) {
|
||||
if (!providersToResolve.includes(undefined) && !providersToResolve.includes(provider.chatSessionType)) {
|
||||
continue; // skip: not considered for resolving
|
||||
@@ -243,7 +249,7 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
|
||||
});
|
||||
}
|
||||
|
||||
sessions.set(session.resource, {
|
||||
sessions.set(session.resource, this.toAgentSession({
|
||||
providerType: provider.chatSessionType,
|
||||
providerLabel,
|
||||
resource: session.resource,
|
||||
@@ -252,7 +258,7 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
|
||||
icon,
|
||||
tooltip: session.tooltip,
|
||||
status,
|
||||
archived: session.archived ?? false,
|
||||
archived: session.archived,
|
||||
timing: {
|
||||
startTime: session.timing.startTime,
|
||||
endTime: session.timing.endTime,
|
||||
@@ -260,7 +266,7 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
|
||||
finishedOrFailedTime
|
||||
},
|
||||
statistics: session.statistics,
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -281,11 +287,39 @@ export class AgentSessionsViewModel extends Disposable implements IAgentSessions
|
||||
|
||||
this._onDidChangeSessions.fire();
|
||||
}
|
||||
|
||||
private toAgentSession(data: IInternalAgentSessionData): IInternalAgentSession {
|
||||
return {
|
||||
...data,
|
||||
isArchived: () => this.isArchived(data),
|
||||
setArchived: (archived: boolean) => this.setArchived(data, archived)
|
||||
};
|
||||
}
|
||||
|
||||
//#region States
|
||||
|
||||
private readonly sessionStates: ResourceMap<{ archived: boolean }>;
|
||||
|
||||
private isArchived(session: IInternalAgentSessionData): boolean {
|
||||
return this.sessionStates.get(session.resource)?.archived ?? Boolean(session.archived);
|
||||
}
|
||||
|
||||
private setArchived(session: IInternalAgentSessionData, archived: boolean): void {
|
||||
if (archived === this.isArchived(session)) {
|
||||
return; // no change
|
||||
}
|
||||
|
||||
this.sessionStates.set(session.resource, { archived });
|
||||
|
||||
this._onDidChangeSessions.fire();
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
//#region Sessions Cache
|
||||
|
||||
interface ISerializedAgentSessionViewModel {
|
||||
interface ISerializedAgentSession {
|
||||
|
||||
readonly providerType: string;
|
||||
readonly providerLabel: string;
|
||||
@@ -300,7 +334,7 @@ interface ISerializedAgentSessionViewModel {
|
||||
readonly tooltip?: string | IMarkdownString;
|
||||
|
||||
readonly status: ChatSessionStatus;
|
||||
readonly archived: boolean;
|
||||
readonly archived: boolean | undefined;
|
||||
|
||||
readonly timing: {
|
||||
readonly startTime: number;
|
||||
@@ -314,14 +348,24 @@ interface ISerializedAgentSessionViewModel {
|
||||
};
|
||||
}
|
||||
|
||||
interface ISerializedAgentSessionState {
|
||||
readonly resource: UriComponents;
|
||||
readonly archived: boolean;
|
||||
}
|
||||
|
||||
class AgentSessionsCache {
|
||||
|
||||
private static readonly STORAGE_KEY = 'agentSessions.cache';
|
||||
private static readonly SESSIONS_STORAGE_KEY = 'agentSessions.model.cache';
|
||||
private static readonly STATE_STORAGE_KEY = 'agentSessions.state.cache';
|
||||
|
||||
constructor(@IStorageService private readonly storageService: IStorageService) { }
|
||||
constructor(
|
||||
@IStorageService private readonly storageService: IStorageService
|
||||
) { }
|
||||
|
||||
saveCachedSessions(sessions: IAgentSessionViewModel[]): void {
|
||||
const serialized: ISerializedAgentSessionViewModel[] = sessions
|
||||
//#region Sessions
|
||||
|
||||
saveCachedSessions(sessions: IInternalAgentSessionData[]): void {
|
||||
const serialized: ISerializedAgentSession[] = sessions
|
||||
.filter(session =>
|
||||
// Only consider providers that we own where we know that
|
||||
// we can also invalidate the data after startup
|
||||
@@ -351,17 +395,18 @@ class AgentSessionsCache {
|
||||
|
||||
statistics: session.statistics,
|
||||
}));
|
||||
this.storageService.store(AgentSessionsCache.STORAGE_KEY, JSON.stringify(serialized), StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
|
||||
this.storageService.store(AgentSessionsCache.SESSIONS_STORAGE_KEY, JSON.stringify(serialized), StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
loadCachedSessions(): IAgentSessionViewModel[] {
|
||||
const sessionsCache = this.storageService.get(AgentSessionsCache.STORAGE_KEY, StorageScope.WORKSPACE);
|
||||
loadCachedSessions(): IInternalAgentSessionData[] {
|
||||
const sessionsCache = this.storageService.get(AgentSessionsCache.SESSIONS_STORAGE_KEY, StorageScope.WORKSPACE);
|
||||
if (!sessionsCache) {
|
||||
return [];
|
||||
}
|
||||
|
||||
try {
|
||||
const cached = JSON.parse(sessionsCache) as ISerializedAgentSessionViewModel[];
|
||||
const cached = JSON.parse(sessionsCache) as ISerializedAgentSession[];
|
||||
return cached.map(session => ({
|
||||
providerType: session.providerType,
|
||||
providerLabel: session.providerLabel,
|
||||
@@ -387,6 +432,44 @@ class AgentSessionsCache {
|
||||
return []; // invalid data in storage, fallback to empty sessions list
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region States
|
||||
|
||||
private static readonly STATES_SCOPE = StorageScope.APPLICATION; // use application scope to track globally
|
||||
|
||||
saveSessionStates(states: ResourceMap<{ archived: boolean }>): void {
|
||||
const serialized: ISerializedAgentSessionState[] = Array.from(states.entries()).map(([resource, state]) => ({
|
||||
resource: resource.toJSON(),
|
||||
archived: state.archived
|
||||
}));
|
||||
|
||||
this.storageService.store(AgentSessionsCache.STATE_STORAGE_KEY, JSON.stringify(serialized), AgentSessionsCache.STATES_SCOPE, StorageTarget.MACHINE);
|
||||
}
|
||||
|
||||
loadSessionStates(): ResourceMap<{ archived: boolean }> {
|
||||
const states = new ResourceMap<{ archived: boolean }>();
|
||||
|
||||
const statesCache = this.storageService.get(AgentSessionsCache.STATE_STORAGE_KEY, AgentSessionsCache.STATES_SCOPE);
|
||||
if (!statesCache) {
|
||||
return states;
|
||||
}
|
||||
|
||||
try {
|
||||
const cached = JSON.parse(statesCache) as ISerializedAgentSessionState[];
|
||||
|
||||
for (const entry of cached) {
|
||||
states.set(URI.revive(entry.resource), { archived: entry.archived });
|
||||
}
|
||||
} catch {
|
||||
// invalid data in storage, fallback to empty states
|
||||
}
|
||||
|
||||
return states;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -0,0 +1,36 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from '../../../../../base/common/lifecycle.js';
|
||||
import { createDecorator, IInstantiationService } from '../../../../../platform/instantiation/common/instantiation.js';
|
||||
import { AgentSessionsModel, IAgentSessionsModel } from './agentSessionsModel.js';
|
||||
|
||||
export interface IAgentSessionsService {
|
||||
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
readonly model: IAgentSessionsModel;
|
||||
}
|
||||
|
||||
export class AgentSessionsService extends Disposable implements IAgentSessionsService {
|
||||
|
||||
declare readonly _serviceBrand: undefined;
|
||||
|
||||
private _model: IAgentSessionsModel | undefined;
|
||||
get model(): IAgentSessionsModel {
|
||||
if (!this._model) {
|
||||
this._model = this._register(this.instantiationService.createInstance(AgentSessionsModel));
|
||||
this._model.resolve(undefined /* all providers */);
|
||||
}
|
||||
|
||||
return this._model;
|
||||
}
|
||||
|
||||
constructor(@IInstantiationService private readonly instantiationService: IInstantiationService) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
export const IAgentSessionsService = createDecorator<IAgentSessionsService>('agentSessions');
|
||||
@@ -24,7 +24,7 @@ import { IOpenerService } from '../../../../../platform/opener/common/opener.js'
|
||||
import { IThemeService } from '../../../../../platform/theme/common/themeService.js';
|
||||
import { IOpenEvent, WorkbenchCompressibleAsyncDataTree } from '../../../../../platform/list/browser/listService.js';
|
||||
import { $, append } from '../../../../../base/browser/dom.js';
|
||||
import { AgentSessionsViewModel, IAgentSessionViewModel, IAgentSessionsViewModel, isLocalAgentSessionItem } from './agentSessionViewModel.js';
|
||||
import { IAgentSession, IAgentSessionsModel, isLocalAgentSessionItem } from './agentSessionsModel.js';
|
||||
import { AgentSessionRenderer, AgentSessionsAccessibilityProvider, AgentSessionsCompressionDelegate, AgentSessionsDataSource, AgentSessionsDragAndDrop, AgentSessionsIdentityProvider, AgentSessionsKeyboardNavigationLabelProvider, AgentSessionsListDelegate, AgentSessionsSorter } from './agentSessionsViewer.js';
|
||||
import { defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js';
|
||||
import { ButtonWithDropdown } from '../../../../../base/browser/ui/button/button.js';
|
||||
@@ -52,11 +52,11 @@ import { TreeFindMode } from '../../../../../base/browser/ui/tree/abstractTree.j
|
||||
import { SIDE_GROUP } from '../../../../services/editor/common/editorService.js';
|
||||
import { IMarshalledChatSessionContext } from '../actions/chatSessionActions.js';
|
||||
import { distinct } from '../../../../../base/common/arrays.js';
|
||||
import { IAgentSessionsService } from './agentSessionsService.js';
|
||||
import { AgentSessionsFilter } from './agentSessionsFilter.js';
|
||||
|
||||
export class AgentSessionsView extends ViewPane {
|
||||
|
||||
private sessionsViewModel: IAgentSessionsViewModel | undefined;
|
||||
|
||||
constructor(
|
||||
options: IViewPaneOptions,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@@ -75,6 +75,7 @@ export class AgentSessionsView extends ViewPane {
|
||||
@IChatService private readonly chatService: IChatService,
|
||||
@IMenuService private readonly menuService: IMenuService,
|
||||
@IChatWidgetService private readonly chatWidgetService: IChatWidgetService,
|
||||
@IAgentSessionsService private readonly agentSessionsService: IAgentSessionsService,
|
||||
) {
|
||||
super({ ...options, titleMenuId: MenuId.AgentSessionsTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, hoverService);
|
||||
}
|
||||
@@ -96,17 +97,10 @@ export class AgentSessionsView extends ViewPane {
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
|
||||
// Sessions List
|
||||
const list = assertReturnsDefined(this.list);
|
||||
this._register(this.onDidChangeBodyVisibility(visible => {
|
||||
if (!visible || this.sessionsViewModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.sessionsViewModel) {
|
||||
this.createViewModel();
|
||||
} else {
|
||||
this._register(this.onDidChangeBodyVisibility(visible => {
|
||||
if (visible) {
|
||||
this.list?.updateChildren();
|
||||
}
|
||||
}));
|
||||
@@ -126,7 +120,7 @@ export class AgentSessionsView extends ViewPane {
|
||||
}));
|
||||
}
|
||||
|
||||
private async openAgentSession(e: IOpenEvent<IAgentSessionViewModel | undefined>): Promise<void> {
|
||||
private async openAgentSession(e: IOpenEvent<IAgentSession | undefined>): Promise<void> {
|
||||
const session = e.element;
|
||||
if (!session) {
|
||||
return;
|
||||
@@ -153,7 +147,7 @@ export class AgentSessionsView extends ViewPane {
|
||||
await this.chatWidgetService.openSession(session.resource, group, options);
|
||||
}
|
||||
|
||||
private async showContextMenu({ element: session, anchor }: ITreeContextMenuEvent<IAgentSessionViewModel>): Promise<void> {
|
||||
private async showContextMenu({ element: session, anchor }: ITreeContextMenuEvent<IAgentSession>): Promise<void> {
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
@@ -272,9 +266,14 @@ export class AgentSessionsView extends ViewPane {
|
||||
//#region Sessions List
|
||||
|
||||
private listContainer: HTMLElement | undefined;
|
||||
private list: WorkbenchCompressibleAsyncDataTree<IAgentSessionsViewModel, IAgentSessionViewModel, FuzzyScore> | undefined;
|
||||
private list: WorkbenchCompressibleAsyncDataTree<IAgentSessionsModel, IAgentSession, FuzzyScore> | undefined;
|
||||
private listFilter: AgentSessionsFilter | undefined;
|
||||
|
||||
private createList(container: HTMLElement): void {
|
||||
this.listFilter = this._register(this.instantiationService.createInstance(AgentSessionsFilter, {
|
||||
filterMenuId: MenuId.AgentSessionsFilterSubMenu,
|
||||
}));
|
||||
|
||||
this.listContainer = append(container, $('.agent-sessions-viewer'));
|
||||
|
||||
this.list = this._register(this.instantiationService.createInstance(WorkbenchCompressibleAsyncDataTree,
|
||||
@@ -285,7 +284,7 @@ export class AgentSessionsView extends ViewPane {
|
||||
[
|
||||
this.instantiationService.createInstance(AgentSessionRenderer)
|
||||
],
|
||||
new AgentSessionsDataSource(),
|
||||
new AgentSessionsDataSource(this.listFilter),
|
||||
{
|
||||
accessibilityProvider: new AgentSessionsAccessibilityProvider(),
|
||||
dnd: this.instantiationService.createInstance(AgentSessionsDragAndDrop),
|
||||
@@ -295,27 +294,27 @@ export class AgentSessionsView extends ViewPane {
|
||||
findWidgetEnabled: true,
|
||||
defaultFindMode: TreeFindMode.Filter,
|
||||
keyboardNavigationLabelProvider: new AgentSessionsKeyboardNavigationLabelProvider(),
|
||||
sorter: new AgentSessionsSorter(),
|
||||
sorter: this.instantiationService.createInstance(AgentSessionsSorter),
|
||||
paddingBottom: AgentSessionsListDelegate.ITEM_HEIGHT,
|
||||
twistieAdditionalCssClass: () => 'force-no-twistie',
|
||||
}
|
||||
)) as WorkbenchCompressibleAsyncDataTree<IAgentSessionsViewModel, IAgentSessionViewModel, FuzzyScore>;
|
||||
}
|
||||
)) as WorkbenchCompressibleAsyncDataTree<IAgentSessionsModel, IAgentSession, FuzzyScore>;
|
||||
|
||||
private createViewModel(): void {
|
||||
const sessionsViewModel = this.sessionsViewModel = this._register(this.instantiationService.createInstance(AgentSessionsViewModel, { filterMenuId: MenuId.AgentSessionsFilterSubMenu }));
|
||||
this.list?.setInput(sessionsViewModel);
|
||||
const model = this.agentSessionsService.model;
|
||||
|
||||
this._register(sessionsViewModel.onDidChangeSessions(() => {
|
||||
this._register(Event.any(
|
||||
this.listFilter.onDidChange,
|
||||
model.onDidChangeSessions
|
||||
)(() => {
|
||||
if (this.isBodyVisible()) {
|
||||
this.list?.updateChildren();
|
||||
}
|
||||
}));
|
||||
|
||||
const didResolveDisposable = this._register(new MutableDisposable());
|
||||
this._register(sessionsViewModel.onWillResolve(() => {
|
||||
this._register(model.onWillResolve(() => {
|
||||
const didResolve = new DeferredPromise<void>();
|
||||
didResolveDisposable.value = Event.once(sessionsViewModel.onDidResolve)(() => didResolve.complete());
|
||||
didResolveDisposable.value = Event.once(model.onDidResolve)(() => didResolve.complete());
|
||||
|
||||
this.progressService.withProgress(
|
||||
{
|
||||
@@ -326,6 +325,8 @@ export class AgentSessionsView extends ViewPane {
|
||||
() => didResolve.p
|
||||
);
|
||||
}));
|
||||
|
||||
this.list?.setInput(model);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
@@ -337,7 +338,7 @@ export class AgentSessionsView extends ViewPane {
|
||||
}
|
||||
|
||||
refresh(): void {
|
||||
this.sessionsViewModel?.resolve(undefined);
|
||||
this.agentSessionsService.model.resolve(undefined);
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ICompressedTreeNode } from '../../../../../base/browser/ui/tree/compres
|
||||
import { ICompressibleKeyboardNavigationLabelProvider, ICompressibleTreeRenderer } from '../../../../../base/browser/ui/tree/objectTree.js';
|
||||
import { ITreeNode, ITreeElementRenderDetails, IAsyncDataSource, ITreeSorter, ITreeDragAndDrop, ITreeDragOverReaction } from '../../../../../base/browser/ui/tree/tree.js';
|
||||
import { Disposable, DisposableStore, IDisposable } from '../../../../../base/common/lifecycle.js';
|
||||
import { IAgentSessionViewModel, IAgentSessionsViewModel, isAgentSession, isAgentSessionsViewModel } from './agentSessionViewModel.js';
|
||||
import { IAgentSession, IAgentSessionsModel, isAgentSession, isAgentSessionsModel } from './agentSessionsModel.js';
|
||||
import { IconLabel } from '../../../../../base/browser/ui/iconLabel/iconLabel.js';
|
||||
import { ThemeIcon } from '../../../../../base/common/themables.js';
|
||||
import { Codicon } from '../../../../../base/common/codicons.js';
|
||||
@@ -37,6 +37,11 @@ import { AGENT_SESSIONS_VIEW_ID } from './agentSessions.js';
|
||||
import { IntervalTimer } from '../../../../../base/common/async.js';
|
||||
import { ActionBar } from '../../../../../base/browser/ui/actionbar/actionbar.js';
|
||||
import { AgentSessionDiffActionViewItem, AgentSessionShowDiffAction } from './agentSessionsActions.js';
|
||||
import { MenuWorkbenchToolBar } from '../../../../../platform/actions/browser/toolbar.js';
|
||||
import { MenuId } from '../../../../../platform/actions/common/actions.js';
|
||||
import { IContextKeyService } from '../../../../../platform/contextkey/common/contextkey.js';
|
||||
import { ChatContextKeys } from '../../common/chatContextKeys.js';
|
||||
import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js';
|
||||
|
||||
interface IAgentSessionItemTemplate {
|
||||
readonly element: HTMLElement;
|
||||
@@ -46,17 +51,19 @@ interface IAgentSessionItemTemplate {
|
||||
|
||||
// Column 2 Row 1
|
||||
readonly title: IconLabel;
|
||||
readonly titleToolbar: MenuWorkbenchToolBar;
|
||||
|
||||
// Column 2 Row 2
|
||||
readonly toolbar: ActionBar;
|
||||
readonly detailsToolbar: ActionBar;
|
||||
readonly description: HTMLElement;
|
||||
readonly status: HTMLElement;
|
||||
|
||||
readonly contextKeyService: IContextKeyService;
|
||||
readonly elementDisposable: DisposableStore;
|
||||
readonly disposables: IDisposable;
|
||||
}
|
||||
|
||||
export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSessionViewModel, FuzzyScore, IAgentSessionItemTemplate> {
|
||||
export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSession, FuzzyScore, IAgentSessionItemTemplate> {
|
||||
|
||||
static readonly TEMPLATE_ID = 'agent-session';
|
||||
|
||||
@@ -69,6 +76,7 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
|
||||
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
|
||||
@IHoverService private readonly hoverService: IHoverService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
) { }
|
||||
|
||||
renderTemplate(container: HTMLElement): IAgentSessionItemTemplate {
|
||||
@@ -84,9 +92,10 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
|
||||
h('div.agent-session-main-col', [
|
||||
h('div.agent-session-title-row', [
|
||||
h('div.agent-session-title@title'),
|
||||
h('div.agent-session-title-toolbar@titleToolbar'),
|
||||
]),
|
||||
h('div.agent-session-details-row', [
|
||||
h('div.agent-session-toolbar@toolbar'),
|
||||
h('div.agent-session-details-toolbar@detailsToolbar'),
|
||||
h('div.agent-session-description@description'),
|
||||
h('div.agent-session-status@status')
|
||||
])
|
||||
@@ -94,9 +103,13 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
|
||||
]
|
||||
);
|
||||
|
||||
container.appendChild(elements.item);
|
||||
const contextKeyService = disposables.add(this.contextKeyService.createScoped(elements.item));
|
||||
const scopedInstantiationService = disposables.add(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, contextKeyService])));
|
||||
const titleToolbar = disposables.add(scopedInstantiationService.createInstance(MenuWorkbenchToolBar, elements.titleToolbar, MenuId.AgentSessionItemToolbar, {
|
||||
menuOptions: { shouldForwardArgs: true },
|
||||
}));
|
||||
|
||||
const toolbar = disposables.add(new ActionBar(elements.toolbar, {
|
||||
const detailsToolbar = disposables.add(new ActionBar(elements.detailsToolbar, {
|
||||
actionViewItemProvider: (action, options) => {
|
||||
if (action.id === AgentSessionShowDiffAction.ID) {
|
||||
return this.instantiationService.createInstance(AgentSessionDiffActionViewItem, action, options);
|
||||
@@ -106,23 +119,27 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
|
||||
},
|
||||
}));
|
||||
|
||||
container.appendChild(elements.item);
|
||||
|
||||
return {
|
||||
element: elements.item,
|
||||
icon: elements.icon,
|
||||
title: disposables.add(new IconLabel(elements.title, { supportHighlights: true, supportIcons: true })),
|
||||
toolbar,
|
||||
titleToolbar,
|
||||
detailsToolbar,
|
||||
description: elements.description,
|
||||
status: elements.status,
|
||||
contextKeyService,
|
||||
elementDisposable,
|
||||
disposables
|
||||
};
|
||||
}
|
||||
|
||||
renderElement(session: ITreeNode<IAgentSessionViewModel, FuzzyScore>, index: number, template: IAgentSessionItemTemplate, details?: ITreeElementRenderDetails): void {
|
||||
renderElement(session: ITreeNode<IAgentSession, FuzzyScore>, index: number, template: IAgentSessionItemTemplate, details?: ITreeElementRenderDetails): void {
|
||||
|
||||
// Clear old state
|
||||
template.elementDisposable.clear();
|
||||
template.toolbar.clear();
|
||||
template.detailsToolbar.clear();
|
||||
template.description.textContent = '';
|
||||
|
||||
// Icon
|
||||
@@ -131,11 +148,15 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
|
||||
// Title
|
||||
template.title.setLabel(session.element.label, undefined, { matches: createMatches(session.filterData) });
|
||||
|
||||
// Diff if provided and finished
|
||||
// Title Actions - Update context keys
|
||||
ChatContextKeys.isArchivedItem.bindTo(template.contextKeyService).set(session.element.isArchived());
|
||||
template.titleToolbar.context = session.element;
|
||||
|
||||
// Details Actions
|
||||
const { statistics: diff } = session.element;
|
||||
if (session.element.status !== ChatSessionStatus.InProgress && diff && (diff.files > 0 || diff.insertions > 0 || diff.deletions > 0)) {
|
||||
const diffAction = template.elementDisposable.add(new AgentSessionShowDiffAction(session.element));
|
||||
template.toolbar.push([diffAction], { icon: false, label: true });
|
||||
template.detailsToolbar.push([diffAction], { icon: false, label: true });
|
||||
}
|
||||
|
||||
// Description otherwise
|
||||
@@ -150,7 +171,7 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
|
||||
this.renderHover(session, template);
|
||||
}
|
||||
|
||||
private getIcon(session: IAgentSessionViewModel): ThemeIcon {
|
||||
private getIcon(session: IAgentSession): ThemeIcon {
|
||||
if (session.status === ChatSessionStatus.InProgress) {
|
||||
return ThemeIcon.modify(Codicon.loading, 'spin');
|
||||
}
|
||||
@@ -162,7 +183,7 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
|
||||
return session.icon;
|
||||
}
|
||||
|
||||
private renderDescription(session: ITreeNode<IAgentSessionViewModel, FuzzyScore>, template: IAgentSessionItemTemplate): void {
|
||||
private renderDescription(session: ITreeNode<IAgentSession, FuzzyScore>, template: IAgentSessionItemTemplate): void {
|
||||
|
||||
// Support description as string
|
||||
if (typeof session.element.description === 'string') {
|
||||
@@ -213,9 +234,9 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
|
||||
return getDurationString(elapsed);
|
||||
}
|
||||
|
||||
private renderStatus(session: ITreeNode<IAgentSessionViewModel, FuzzyScore>, template: IAgentSessionItemTemplate): void {
|
||||
private renderStatus(session: ITreeNode<IAgentSession, FuzzyScore>, template: IAgentSessionItemTemplate): void {
|
||||
|
||||
const getStatus = (session: IAgentSessionViewModel) => {
|
||||
const getStatus = (session: IAgentSession) => {
|
||||
let timeLabel: string | undefined;
|
||||
if (session.status === ChatSessionStatus.InProgress && session.timing.inProgressTime) {
|
||||
timeLabel = this.toDuration(session.timing.inProgressTime, Date.now());
|
||||
@@ -232,7 +253,7 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
|
||||
timer.cancelAndSet(() => template.status.textContent = getStatus(session.element), session.element.status === ChatSessionStatus.InProgress ? 1000 /* every second */ : 60 * 1000 /* every minute */);
|
||||
}
|
||||
|
||||
private renderHover(session: ITreeNode<IAgentSessionViewModel, FuzzyScore>, template: IAgentSessionItemTemplate): void {
|
||||
private renderHover(session: ITreeNode<IAgentSession, FuzzyScore>, template: IAgentSessionItemTemplate): void {
|
||||
const tooltip = session.element.tooltip;
|
||||
if (tooltip) {
|
||||
template.elementDisposable.add(
|
||||
@@ -258,11 +279,11 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
|
||||
}
|
||||
}
|
||||
|
||||
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<IAgentSessionViewModel>, FuzzyScore>, index: number, templateData: IAgentSessionItemTemplate, details?: ITreeElementRenderDetails): void {
|
||||
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<IAgentSession>, FuzzyScore>, index: number, templateData: IAgentSessionItemTemplate, details?: ITreeElementRenderDetails): void {
|
||||
throw new Error('Should never happen since session is incompressible');
|
||||
}
|
||||
|
||||
disposeElement(element: ITreeNode<IAgentSessionViewModel, FuzzyScore>, index: number, template: IAgentSessionItemTemplate, details?: ITreeElementRenderDetails): void {
|
||||
disposeElement(element: ITreeNode<IAgentSession, FuzzyScore>, index: number, template: IAgentSessionItemTemplate, details?: ITreeElementRenderDetails): void {
|
||||
template.elementDisposable.clear();
|
||||
}
|
||||
|
||||
@@ -271,48 +292,56 @@ export class AgentSessionRenderer implements ICompressibleTreeRenderer<IAgentSes
|
||||
}
|
||||
}
|
||||
|
||||
export class AgentSessionsListDelegate implements IListVirtualDelegate<IAgentSessionViewModel> {
|
||||
export class AgentSessionsListDelegate implements IListVirtualDelegate<IAgentSession> {
|
||||
|
||||
static readonly ITEM_HEIGHT = 44;
|
||||
|
||||
getHeight(element: IAgentSessionViewModel): number {
|
||||
getHeight(element: IAgentSession): number {
|
||||
return AgentSessionsListDelegate.ITEM_HEIGHT;
|
||||
}
|
||||
|
||||
getTemplateId(element: IAgentSessionViewModel): string {
|
||||
getTemplateId(element: IAgentSession): string {
|
||||
return AgentSessionRenderer.TEMPLATE_ID;
|
||||
}
|
||||
}
|
||||
|
||||
export class AgentSessionsAccessibilityProvider implements IListAccessibilityProvider<IAgentSessionViewModel> {
|
||||
export class AgentSessionsAccessibilityProvider implements IListAccessibilityProvider<IAgentSession> {
|
||||
|
||||
getWidgetAriaLabel(): string {
|
||||
return localize('agentSessions', "Agent Sessions");
|
||||
}
|
||||
|
||||
getAriaLabel(element: IAgentSessionViewModel): string | null {
|
||||
getAriaLabel(element: IAgentSession): string | null {
|
||||
return element.label;
|
||||
}
|
||||
}
|
||||
|
||||
export class AgentSessionsDataSource implements IAsyncDataSource<IAgentSessionsViewModel, IAgentSessionViewModel> {
|
||||
export interface IAgentSessionsDataFilter {
|
||||
exclude(session: IAgentSession): boolean;
|
||||
}
|
||||
|
||||
hasChildren(element: IAgentSessionsViewModel | IAgentSessionViewModel): boolean {
|
||||
return isAgentSessionsViewModel(element);
|
||||
export class AgentSessionsDataSource implements IAsyncDataSource<IAgentSessionsModel, IAgentSession> {
|
||||
|
||||
constructor(
|
||||
private readonly filter: IAgentSessionsDataFilter
|
||||
) { }
|
||||
|
||||
hasChildren(element: IAgentSessionsModel | IAgentSession): boolean {
|
||||
return isAgentSessionsModel(element);
|
||||
}
|
||||
|
||||
getChildren(element: IAgentSessionsViewModel | IAgentSessionViewModel): Iterable<IAgentSessionViewModel> {
|
||||
if (!isAgentSessionsViewModel(element)) {
|
||||
getChildren(element: IAgentSessionsModel | IAgentSession): Iterable<IAgentSession> {
|
||||
if (!isAgentSessionsModel(element)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return element.sessions;
|
||||
return element.sessions.filter(session => !this.filter.exclude(session));
|
||||
}
|
||||
}
|
||||
|
||||
export class AgentSessionsIdentityProvider implements IIdentityProvider<IAgentSessionsViewModel | IAgentSessionViewModel> {
|
||||
export class AgentSessionsIdentityProvider implements IIdentityProvider<IAgentSessionsModel | IAgentSession> {
|
||||
|
||||
getId(element: IAgentSessionsViewModel | IAgentSessionViewModel): string {
|
||||
getId(element: IAgentSessionsModel | IAgentSession): string {
|
||||
if (isAgentSession(element)) {
|
||||
return element.resource.toString();
|
||||
}
|
||||
@@ -321,16 +350,16 @@ export class AgentSessionsIdentityProvider implements IIdentityProvider<IAgentSe
|
||||
}
|
||||
}
|
||||
|
||||
export class AgentSessionsCompressionDelegate implements ITreeCompressionDelegate<IAgentSessionViewModel> {
|
||||
export class AgentSessionsCompressionDelegate implements ITreeCompressionDelegate<IAgentSession> {
|
||||
|
||||
isIncompressible(element: IAgentSessionViewModel): boolean {
|
||||
isIncompressible(element: IAgentSession): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class AgentSessionsSorter implements ITreeSorter<IAgentSessionViewModel> {
|
||||
export class AgentSessionsSorter implements ITreeSorter<IAgentSession> {
|
||||
|
||||
compare(sessionA: IAgentSessionViewModel, sessionB: IAgentSessionViewModel): number {
|
||||
compare(sessionA: IAgentSession, sessionB: IAgentSession): number {
|
||||
const aInProgress = sessionA.status === ChatSessionStatus.InProgress;
|
||||
const bInProgress = sessionB.status === ChatSessionStatus.InProgress;
|
||||
|
||||
@@ -341,23 +370,33 @@ export class AgentSessionsSorter implements ITreeSorter<IAgentSessionViewModel>
|
||||
return 1; // a (finished) comes after b (in-progress)
|
||||
}
|
||||
|
||||
const aArchived = sessionA.isArchived();
|
||||
const bArchived = sessionB.isArchived();
|
||||
|
||||
if (!aArchived && bArchived) {
|
||||
return -1; // a (non-archived) comes before b (archived)
|
||||
}
|
||||
if (aArchived && !bArchived) {
|
||||
return 1; // a (archived) comes after b (non-archived)
|
||||
}
|
||||
|
||||
// Both in-progress or finished: sort by end or start time (most recent first)
|
||||
return (sessionB.timing.endTime || sessionB.timing.startTime) - (sessionA.timing.endTime || sessionA.timing.startTime);
|
||||
}
|
||||
}
|
||||
|
||||
export class AgentSessionsKeyboardNavigationLabelProvider implements ICompressibleKeyboardNavigationLabelProvider<IAgentSessionViewModel> {
|
||||
export class AgentSessionsKeyboardNavigationLabelProvider implements ICompressibleKeyboardNavigationLabelProvider<IAgentSession> {
|
||||
|
||||
getKeyboardNavigationLabel(element: IAgentSessionViewModel): string {
|
||||
getKeyboardNavigationLabel(element: IAgentSession): string {
|
||||
return element.label;
|
||||
}
|
||||
|
||||
getCompressedNodeKeyboardNavigationLabel(elements: IAgentSessionViewModel[]): { toString(): string | undefined } | undefined {
|
||||
getCompressedNodeKeyboardNavigationLabel(elements: IAgentSession[]): { toString(): string | undefined } | undefined {
|
||||
return undefined; // not enabled
|
||||
}
|
||||
}
|
||||
|
||||
export class AgentSessionsDragAndDrop extends Disposable implements ITreeDragAndDrop<IAgentSessionViewModel> {
|
||||
export class AgentSessionsDragAndDrop extends Disposable implements ITreeDragAndDrop<IAgentSession> {
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
@@ -366,16 +405,16 @@ export class AgentSessionsDragAndDrop extends Disposable implements ITreeDragAnd
|
||||
}
|
||||
|
||||
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
|
||||
const elements = data.getData() as IAgentSessionViewModel[];
|
||||
const elements = data.getData() as IAgentSession[];
|
||||
const uris = coalesce(elements.map(e => e.resource));
|
||||
this.instantiationService.invokeFunction(accessor => fillEditorsDragData(accessor, uris, originalEvent));
|
||||
}
|
||||
|
||||
getDragURI(element: IAgentSessionViewModel): string | null {
|
||||
getDragURI(element: IAgentSession): string | null {
|
||||
return element.resource.toString();
|
||||
}
|
||||
|
||||
getDragLabel?(elements: IAgentSessionViewModel[], originalEvent: DragEvent): string | undefined {
|
||||
getDragLabel?(elements: IAgentSession[], originalEvent: DragEvent): string | undefined {
|
||||
if (elements.length === 1) {
|
||||
return elements[0].label;
|
||||
}
|
||||
@@ -383,9 +422,9 @@ export class AgentSessionsDragAndDrop extends Disposable implements ITreeDragAnd
|
||||
return localize('agentSessions.dragLabel', "{0} agent sessions", elements.length);
|
||||
}
|
||||
|
||||
onDragOver(data: IDragAndDropData, targetElement: IAgentSessionViewModel | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction {
|
||||
onDragOver(data: IDragAndDropData, targetElement: IAgentSession | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction {
|
||||
return false;
|
||||
}
|
||||
|
||||
drop(data: IDragAndDropData, targetElement: IAgentSessionViewModel | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): void { }
|
||||
drop(data: IDragAndDropData, targetElement: IAgentSession | undefined, targetIndex: number | undefined, targetSector: ListViewTargetSector | undefined, originalEvent: DragEvent): void { }
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.agent-sessions-viewer .agent-session-item .agent-session-toolbar {
|
||||
.agent-sessions-viewer .agent-session-item .agent-session-details-toolbar {
|
||||
|
||||
.monaco-action-bar .actions-container .action-item .action-label {
|
||||
padding: 0;
|
||||
@@ -31,7 +31,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.monaco-list-row.selected .agent-session-item .agent-session-toolbar {
|
||||
.monaco-list-row.selected .agent-session-item .agent-session-details-toolbar {
|
||||
|
||||
.agent-session-diff-files,
|
||||
.agent-session-diff-added,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
.agent-sessions-viewer {
|
||||
|
||||
.monaco-list-row > .monaco-tl-row > .monaco-tl-twistie.force-no-twistie {
|
||||
.monaco-list-row .force-no-twistie {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
.monaco-list-row .agent-session-title-toolbar .monaco-toolbar {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.monaco-list-row:hover .agent-session-title-toolbar .monaco-toolbar,
|
||||
.monaco-list-row.focused .agent-session-title-toolbar .monaco-toolbar {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.agent-session-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
@@ -132,6 +132,7 @@ import { PromptUrlHandler } from './promptSyntax/promptUrlHandler.js';
|
||||
import { ConfigureToolSets, UserToolSetsContributions } from './tools/toolSetsContribution.js';
|
||||
import { ChatViewsWelcomeHandler } from './viewsWelcome/chatViewsWelcomeHandler.js';
|
||||
import { ChatWidgetService } from './chatWidgetService.js';
|
||||
import { AgentSessionsService, IAgentSessionsService } from './agentSessions/agentSessionsService.js';
|
||||
|
||||
const toolReferenceNameEnumValues: string[] = [];
|
||||
const toolReferenceNameEnumDescriptions: string[] = [];
|
||||
@@ -1195,6 +1196,7 @@ registerSingleton(IChatAttachmentResolveService, ChatAttachmentResolveService, I
|
||||
registerSingleton(IChatTodoListService, ChatTodoListService, InstantiationType.Delayed);
|
||||
registerSingleton(IChatOutputRendererService, ChatOutputRendererService, InstantiationType.Delayed);
|
||||
registerSingleton(IChatLayoutService, ChatLayoutService, InstantiationType.Delayed);
|
||||
registerSingleton(IAgentSessionsService, AgentSessionsService, InstantiationType.Delayed);
|
||||
|
||||
registerAction2(ConfigureToolSets);
|
||||
registerAction2(RenameChatSessionAction);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user