mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-08 17:19:48 +01:00
Merge pull request #282579 from microsoft/connor4312/282536
chat: store state for empty sessions separately to allow reuse
This commit is contained in:
@@ -63,6 +63,7 @@ import { IKeybindingService } from '../../../../platform/keybinding/common/keybi
|
||||
import { ILabelService } from '../../../../platform/label/common/label.js';
|
||||
import { WorkbenchList } from '../../../../platform/list/browser/listService.js';
|
||||
import { ILogService } from '../../../../platform/log/common/log.js';
|
||||
import { ObservableMemento, observableMemento } from '../../../../platform/observable/common/observableMemento.js';
|
||||
import { bindContextKey } from '../../../../platform/observable/common/platformObservableUtils.js';
|
||||
import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js';
|
||||
import { IThemeService } from '../../../../platform/theme/common/themeService.js';
|
||||
@@ -145,6 +146,13 @@ export interface IWorkingSetEntry {
|
||||
uri: URI;
|
||||
}
|
||||
|
||||
const emptyInputState = observableMemento<IChatModelInputState | undefined>({
|
||||
defaultValue: undefined,
|
||||
key: 'chat.untitledInputState',
|
||||
toStorage: JSON.stringify,
|
||||
fromStorage: JSON.parse,
|
||||
});
|
||||
|
||||
export class ChatInputPart extends Disposable implements IHistoryNavigationWidget {
|
||||
private static _counter = 0;
|
||||
|
||||
@@ -402,6 +410,9 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
*/
|
||||
private _generating?: { rc: number; defer: DeferredPromise<void> };
|
||||
|
||||
private _emptyInputState: ObservableMemento<IChatModelInputState | undefined>;
|
||||
private _chatSessionIsEmpty = false;
|
||||
|
||||
constructor(
|
||||
// private readonly editorOptions: ChatEditorOptions, // TODO this should be used
|
||||
private readonly location: ChatAgentLocation,
|
||||
@@ -437,6 +448,7 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
|
||||
// Initialize debounced text sync scheduler
|
||||
this._syncTextDebounced = this._register(new RunOnceScheduler(() => this._syncInputStateToModel(), 150));
|
||||
this._emptyInputState = this._register(emptyInputState(StorageScope.WORKSPACE, StorageTarget.USER, this.storageService));
|
||||
|
||||
this._contextResourceLabels = this._register(this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this._onDidChangeVisibility.event }));
|
||||
this._currentModeObservable = observableValue<IChatMode>('currentMode', this.options.defaultMode ?? ChatMode.Agent);
|
||||
@@ -739,21 +751,55 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
/**
|
||||
* Set the input model reference for syncing input state
|
||||
*/
|
||||
setInputModel(model: IInputModel | undefined): void {
|
||||
setInputModel(model: IInputModel, chatSessionIsEmpty: boolean): void {
|
||||
this._inputModel = model;
|
||||
this._modelSyncDisposables.clear();
|
||||
this.selectedToolsModel.resetSessionEnablementState();
|
||||
this._chatSessionIsEmpty = chatSessionIsEmpty;
|
||||
|
||||
if (!model) {
|
||||
return;
|
||||
// TODO@roblourens This is for an experiment which will be obsolete in a month or two and can then be removed.
|
||||
if (chatSessionIsEmpty) {
|
||||
this._setEmptyModelState();
|
||||
}
|
||||
|
||||
// Observe changes from model and sync to view
|
||||
this._modelSyncDisposables.add(autorun(reader => {
|
||||
const state = model.state.read(reader);
|
||||
let state = model.state.read(reader);
|
||||
if (!state && this._chatSessionIsEmpty) {
|
||||
state = this._emptyInputState.read(undefined);
|
||||
}
|
||||
|
||||
this._syncFromModel(state);
|
||||
}));
|
||||
}
|
||||
|
||||
private _setEmptyModelState() {
|
||||
const storageKey = this.getDefaultModeExperimentStorageKey();
|
||||
const hasSetDefaultMode = this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false);
|
||||
if (!hasSetDefaultMode) {
|
||||
const isAnonymous = this.entitlementService.anonymous;
|
||||
this.experimentService.getTreatment('chat.defaultMode')
|
||||
.then((defaultModeTreatment => {
|
||||
if (isAnonymous) {
|
||||
// be deterministic for anonymous users
|
||||
// to support agentic flows with default
|
||||
// model.
|
||||
defaultModeTreatment = ChatModeKind.Agent;
|
||||
}
|
||||
|
||||
if (typeof defaultModeTreatment === 'string') {
|
||||
this.storageService.store(storageKey, true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
const defaultMode = validateChatMode(defaultModeTreatment);
|
||||
if (defaultMode) {
|
||||
this.logService.trace(`Applying default mode from experiment: ${defaultMode}`);
|
||||
this.setChatMode(defaultMode, false);
|
||||
this.checkModelSupported();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync from model to view (when model state changes)
|
||||
*/
|
||||
@@ -811,12 +857,17 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
* Sync current input state to the input model
|
||||
*/
|
||||
private _syncInputStateToModel(): void {
|
||||
if (!this._inputModel || this._isSyncingToOrFromInputModel) {
|
||||
if (this._isSyncingToOrFromInputModel) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
this._isSyncingToOrFromInputModel = true;
|
||||
this._inputModel.setState(this.getCurrentInputState());
|
||||
const state = this.getCurrentInputState();
|
||||
if (this._chatSessionIsEmpty) {
|
||||
this._emptyInputState.set(state, undefined);
|
||||
}
|
||||
this._inputModel?.setState(state);
|
||||
this._isSyncingToOrFromInputModel = false;
|
||||
}
|
||||
|
||||
@@ -984,38 +1035,6 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
}
|
||||
}
|
||||
|
||||
initForNewChatModel(state: IChatModelInputState | undefined, chatSessionIsEmpty: boolean): void {
|
||||
this.selectedToolsModel.resetSessionEnablementState();
|
||||
|
||||
// TODO@roblourens This is for an experiment which will be obsolete in a month or two and can then be removed.
|
||||
if (chatSessionIsEmpty) {
|
||||
const storageKey = this.getDefaultModeExperimentStorageKey();
|
||||
const hasSetDefaultMode = this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false);
|
||||
if (!hasSetDefaultMode) {
|
||||
const isAnonymous = this.entitlementService.anonymous;
|
||||
this.experimentService.getTreatment('chat.defaultMode')
|
||||
.then((defaultModeTreatment => {
|
||||
if (isAnonymous) {
|
||||
// be deterministic for anonymous users
|
||||
// to support agentic flows with default
|
||||
// model.
|
||||
defaultModeTreatment = ChatModeKind.Agent;
|
||||
}
|
||||
|
||||
if (typeof defaultModeTreatment === 'string') {
|
||||
this.storageService.store(storageKey, true, StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
const defaultMode = validateChatMode(defaultModeTreatment);
|
||||
if (defaultMode) {
|
||||
this.logService.trace(`Applying default mode from experiment: ${defaultMode}`);
|
||||
this.setChatMode(defaultMode, false);
|
||||
this.checkModelSupported();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getDefaultModeExperimentStorageKey(): string {
|
||||
const tag = this.options.widgetViewKindTag;
|
||||
return `chat.${tag}.hasSetDefaultModeByExperiment`;
|
||||
@@ -1161,6 +1180,11 @@ export class ChatInputPart extends Disposable implements IHistoryNavigationWidge
|
||||
this.history.append(this._getFilteredEntry(userQuery));
|
||||
}
|
||||
|
||||
if (this._chatSessionIsEmpty) {
|
||||
this._chatSessionIsEmpty = false;
|
||||
this._emptyInputState.set(undefined, undefined);
|
||||
}
|
||||
|
||||
// Clear attached context, fire event to clear input state, and clear the input editor
|
||||
this.attachmentModel.clear();
|
||||
this._onDidLoadInputState.fire();
|
||||
|
||||
@@ -1988,7 +1988,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
this.viewModel = this.instantiationService.createInstance(ChatViewModel, model, this._codeBlockModelCollection);
|
||||
|
||||
// Pass input model reference to input part for state syncing
|
||||
this.inputPart.setInputModel(model.inputModel);
|
||||
this.inputPart.setInputModel(model.inputModel, model.getRequests().length === 0);
|
||||
|
||||
if (this._lockedAgent) {
|
||||
let placeholder = this.chatSessionsService.getInputPlaceholderForSessionType(this._lockedAgent.id);
|
||||
@@ -2034,8 +2034,6 @@ export class ChatWidget extends Disposable implements IChatWidget {
|
||||
this.viewModel = undefined;
|
||||
this.onDidChangeItems();
|
||||
}));
|
||||
const inputState = model.inputModel.state.get();
|
||||
this.input.initForNewChatModel(inputState, model.getRequests().length === 0);
|
||||
this._sessionIsEmptyContextKey.set(model.getRequests().length === 0);
|
||||
|
||||
this.refreshParsedInput();
|
||||
|
||||
Reference in New Issue
Block a user