InputOnTop is the winner (#189584)

Removes the old modes so we can focus on the inputOnTop mode.
This commit is contained in:
Tyler James Leonhardt
2023-08-03 16:14:42 -07:00
committed by GitHub
parent 1d28efe34c
commit 34e548d9bb
6 changed files with 257 additions and 626 deletions
@@ -3,12 +3,263 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { registerAction2 } from 'vs/platform/actions/common/actions';
import { AskQuickQuestionAction } from 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction';
import 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/singleQuickQuestionAction';
import 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/multipleByScrollQuickQuestionAction';
import * as dom from 'vs/base/browser/dom';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Codicon } from 'vs/base/common/codicons';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { ThemeIcon } from 'vs/base/common/themables';
import { localize } from 'vs/nls';
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
import { ContextKeyExpr, IContextKeyService, IScopedContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { editorBackground, editorForeground } from 'vs/platform/theme/common/colorRegistry';
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane';
import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
export const ASK_QUICK_QUESTION_ACTION_ID = 'chat.action.askQuickQuestion';
export function registerChatQuickQuestionActions() {
registerAction2(AskQuickQuestionAction);
}
class AskQuickQuestionAction extends Action2 {
private _currentTimer: any | undefined;
private _input: IQuickPick<IQuickPickItem> | undefined;
private _currentChat: QuickChat | undefined;
constructor() {
super({
id: ASK_QUICK_QUESTION_ACTION_ID,
title: { value: localize('chat', "Chat"), original: 'Chat' },
precondition: CONTEXT_PROVIDER_EXISTS,
icon: Codicon.commentDiscussion,
f1: false,
category: CHAT_CATEGORY,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI,
linux: {
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KeyI
}
},
menu: {
id: MenuId.LayoutControlMenu,
group: '0_workbench_toggles',
when: ContextKeyExpr.notEquals('config.chat.experimental.defaultMode', 'chatView'),
order: 0
}
});
}
override run(accessor: ServicesAccessor, query: string): void {
const quickInputService = accessor.get(IQuickInputService);
const chatService = accessor.get(IChatService);
const instantiationService = accessor.get(IInstantiationService);
// First things first, clear the existing timer that will dispose the session
clearTimeout(this._currentTimer);
this._currentTimer = undefined;
// If the input is already shown, hide it. This provides a toggle behavior of the quick pick
if (this._input !== undefined) {
this._input.hide();
return;
}
// Check if any providers are available. If not, show nothing
// This shouldn't be needed because of the precondition, but just in case
const providerInfo = chatService.getProviderInfos()[0];
if (!providerInfo) {
return;
}
const disposableStore = new DisposableStore();
//#region Setup quick pick
this._input = quickInputService.createQuickPick();
disposableStore.add(this._input);
this._input.hideInput = true;
const containerSession = dom.$('.interactive-session');
this._input.widget = containerSession;
this._currentChat ??= instantiationService.createInstance(QuickChat, {
providerId: providerInfo.id,
});
// show needs to come before the current chat rendering
this._input.show();
this._currentChat.render(containerSession);
const clearButton = {
iconClass: ThemeIcon.asClassName(Codicon.clearAll),
tooltip: localize('clear', "Clear"),
};
this._input.buttons = [
clearButton,
{
iconClass: ThemeIcon.asClassName(Codicon.commentDiscussion),
tooltip: localize('openInChat', "Open In Chat View"),
}
];
this._input.title = providerInfo.displayName;
disposableStore.add(this._input.onDidHide(() => {
disposableStore.dispose();
this._input = undefined;
this._currentTimer = setTimeout(() => {
this._currentChat?.dispose();
this._currentChat = undefined;
}, 1000 * 30); // 30 seconds
}));
//#endregion
disposableStore.add(this._input.onDidAccept(() => {
this._currentChat?.acceptInput();
}));
disposableStore.add(this._input.onDidTriggerButton((e) => {
if (e === clearButton) {
this._currentChat?.clear();
} else {
this._currentChat?.openChatView();
}
}));
this._currentChat.focus();
if (query) {
this._currentChat.setValue(query);
this._currentChat.acceptInput();
}
}
}
class QuickChat extends Disposable {
private widget!: ChatWidget;
private model: ChatModel | undefined;
private _currentQuery: string | undefined;
private _scopedContextKeyService!: IScopedContextKeyService;
get scopedContextKeyService() {
return this._scopedContextKeyService;
}
constructor(
private readonly _options: IChatViewOptions,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IChatService private readonly chatService: IChatService,
@IChatWidgetService private readonly _chatWidgetService: IChatWidgetService
) {
super();
}
clear() {
this.model?.dispose();
this.model = undefined;
this.updateModel();
this.widget.inputEditor.setValue('');
}
focus(): void {
if (this.widget) {
this.widget.focusInput();
}
}
render(parent: HTMLElement): void {
this._scopedContextKeyService?.dispose();
this._scopedContextKeyService = this._register(this.contextKeyService.createScoped(parent));
const scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]));
this.widget?.dispose();
this.widget = this._register(
scopedInstantiationService.createInstance(
ChatWidget,
{ resource: true, renderInputOnTop: true },
{
listForeground: editorForeground,
listBackground: editorBackground,
inputEditorBackground: SIDE_BAR_BACKGROUND,
resultEditorBackground: SIDE_BAR_BACKGROUND
}));
this.widget.render(parent);
this.widget.setVisible(true);
this.widget.setDynamicChatTreeItemLayout(2, 600);
this.updateModel();
if (this._currentQuery) {
this.widget.inputEditor.setSelection({
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: this._currentQuery.length + 1
});
}
this.registerListeners();
}
private registerListeners(): void {
this._register(this.widget.inputEditor.onDidChangeModelContent((e) => {
this._currentQuery = this.widget.inputEditor.getValue();
}));
}
async acceptInput(): Promise<void> {
if (this.widget.inputEditor.getValue().trim() === '/clear') {
this.clear();
} else {
await this.widget.acceptInput();
}
}
async openChatView(): Promise<void> {
const widget = await this._chatWidgetService.revealViewForProvider(this._options.providerId);
if (!widget?.viewModel || !this.model) {
return;
}
for (const request of this.model.getRequests()) {
if (request.response?.response.value || request.response?.errorDetails) {
this.chatService.addCompleteRequest(widget.viewModel.sessionId,
request.message as string,
{
message: request.response.response.value,
errorDetails: request.response.errorDetails
});
} else if (request.message) {
}
}
const value = this.widget.inputEditor.getValue();
if (value) {
widget.inputEditor.setValue(value);
}
widget.focusInput();
}
setValue(value: string): void {
this.widget.inputEditor.setValue(value);
}
private updateModel(): void {
this.model ??= this.chatService.startSession(this._options.providerId, CancellationToken.None);
if (!this.model) {
throw new Error('Could not start chat session');
}
this.widget.setModel(this.model, { inputValue: this._currentQuery });
}
}
@@ -1,280 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Codicon } from 'vs/base/common/codicons';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { ThemeIcon } from 'vs/base/common/themables';
import { localize } from 'vs/nls';
import { IContextKeyService, IScopedContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { editorForeground, editorBackground } from 'vs/platform/theme/common/colorRegistry';
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { AskQuickQuestionAction, IQuickQuestionMode, QuickQuestionMode } from 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction';
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane';
import { ChatWidget } from 'vs/workbench/contrib/chat/browser/chatWidget';
import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
interface IChatQuickQuestionModeOptions {
renderInputOnTop: boolean;
useDynamicMessageLayout: boolean;
}
class BaseChatQuickQuestionMode implements IQuickQuestionMode {
private _currentTimer: any | undefined;
private _input: IQuickPick<IQuickPickItem> | undefined;
private _currentChat: QuickChat | undefined;
constructor(
private readonly _options: IChatQuickQuestionModeOptions
) { }
run(accessor: ServicesAccessor, query: string): void {
const quickInputService = accessor.get(IQuickInputService);
const chatService = accessor.get(IChatService);
const instantiationService = accessor.get(IInstantiationService);
// First things first, clear the existing timer that will dispose the session
clearTimeout(this._currentTimer);
this._currentTimer = undefined;
// If the input is already shown, hide it. This provides a toggle behavior of the quick pick
if (this._input !== undefined) {
this._input.hide();
return;
}
// Check if any providers are available. If not, show nothing
// This shouldn't be needed because of the precondition, but just in case
const providerInfo = chatService.getProviderInfos()[0];
if (!providerInfo) {
return;
}
const disposableStore = new DisposableStore();
//#region Setup quick pick
this._input = quickInputService.createQuickPick();
disposableStore.add(this._input);
this._input.hideInput = true;
const containerSession = dom.$('.interactive-session');
this._input.widget = containerSession;
this._currentChat ??= instantiationService.createInstance(QuickChat, {
providerId: providerInfo.id,
...this._options
});
// show needs to come before the current chat rendering
this._input.show();
this._currentChat.render(containerSession);
const clearButton = {
iconClass: ThemeIcon.asClassName(Codicon.clearAll),
tooltip: localize('clear', "Clear"),
};
this._input.buttons = [
clearButton,
{
iconClass: ThemeIcon.asClassName(Codicon.commentDiscussion),
tooltip: localize('openInChat', "Open In Chat View"),
}
];
this._input.title = providerInfo.displayName;
disposableStore.add(this._input.onDidHide(() => {
disposableStore.dispose();
this._input = undefined;
this._currentTimer = setTimeout(() => {
this._currentChat?.dispose();
this._currentChat = undefined;
}, 1000 * 30); // 30 seconds
}));
//#endregion
disposableStore.add(this._input.onDidAccept(() => {
this._currentChat?.acceptInput();
}));
disposableStore.add(this._input.onDidTriggerButton((e) => {
if (e === clearButton) {
this._currentChat?.clear();
} else {
this._currentChat?.openChatView();
}
}));
this._currentChat.layout();
this._currentChat.focus();
if (query) {
this._currentChat.setValue(query);
this._currentChat.acceptInput();
}
}
}
class QuickChat extends Disposable {
private widget!: ChatWidget;
private model: ChatModel | undefined;
private _currentQuery: string | undefined;
private _scopedContextKeyService!: IScopedContextKeyService;
get scopedContextKeyService() {
return this._scopedContextKeyService;
}
private _currentParentElement?: HTMLElement;
constructor(
private readonly _options: IChatViewOptions & IChatQuickQuestionModeOptions,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IChatService private readonly chatService: IChatService,
@IChatWidgetService private readonly _chatWidgetService: IChatWidgetService
) {
super();
}
clear() {
this.model?.dispose();
this.model = undefined;
this.updateModel();
this.widget.inputEditor.setValue('');
}
focus(): void {
if (this.widget) {
this.widget.focusInput();
}
}
render(parent: HTMLElement): void {
this._currentParentElement = parent;
this._scopedContextKeyService?.dispose();
this._scopedContextKeyService = this._register(this.contextKeyService.createScoped(parent));
const scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService]));
this.widget?.dispose();
this.widget = this._register(
scopedInstantiationService.createInstance(
ChatWidget,
{ resource: true, renderInputOnTop: this._options.renderInputOnTop },
{
listForeground: editorForeground,
listBackground: editorBackground,
inputEditorBackground: SIDE_BAR_BACKGROUND,
resultEditorBackground: SIDE_BAR_BACKGROUND
}));
this.widget.render(parent);
this.widget.setVisible(true);
if (this._options.useDynamicMessageLayout) {
this.widget.setDynamicChatTreeItemLayout(2, 600);
}
this.updateModel();
if (this._currentQuery) {
this.widget.inputEditor.setSelection({
startLineNumber: 1,
startColumn: 1,
endLineNumber: 1,
endColumn: this._currentQuery.length + 1
});
}
this.registerListeners();
}
private registerListeners(): void {
this._register(dom.addDisposableListener(parent, dom.EventType.RESIZE, () => this.layout()));
this._register(this.widget.inputEditor.onDidChangeModelContent((e) => {
this._currentQuery = this.widget.inputEditor.getValue();
}));
}
async acceptInput(): Promise<void> {
if (this.widget.inputEditor.getValue().trim() === '/clear') {
this.clear();
} else {
await this.widget.acceptInput();
}
this.layout();
}
async openChatView(): Promise<void> {
const widget = await this._chatWidgetService.revealViewForProvider(this._options.providerId);
if (!widget?.viewModel || !this.model) {
return;
}
for (const request of this.model.getRequests()) {
if (request.response?.response.value || request.response?.errorDetails) {
this.chatService.addCompleteRequest(widget.viewModel.sessionId,
request.message as string,
{
message: request.response.response.value,
errorDetails: request.response.errorDetails
});
} else if (request.message) {
}
}
const value = this.widget.inputEditor.getValue();
if (value) {
widget.inputEditor.setValue(value);
}
widget.focusInput();
}
setValue(value: string): void {
this.widget.inputEditor.setValue(value);
}
layout(): void {
if (!this._options.useDynamicMessageLayout && this._currentParentElement) {
this.widget.layout(500, this._currentParentElement.offsetWidth);
}
}
private updateModel(): void {
this.model ??= this.chatService.startSession(this._options.providerId, CancellationToken.None);
if (!this.model) {
throw new Error('Could not start chat session');
}
this.widget.setModel(this.model, { inputValue: this._currentQuery });
}
}
AskQuickQuestionAction.registerMode(
QuickQuestionMode.InputOnTopChat,
class InputOnTopChatQuickQuestionMode extends BaseChatQuickQuestionMode {
constructor() {
super({
renderInputOnTop: true,
useDynamicMessageLayout: true
});
}
}
);
AskQuickQuestionAction.registerMode(
QuickQuestionMode.InputOnBottomChat,
class InputOnBottomChatQuickQuestionMode extends BaseChatQuickQuestionMode {
constructor() {
super({
renderInputOnTop: false,
useDynamicMessageLayout: false
});
}
}
);
@@ -1,71 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Codicon } from 'vs/base/common/codicons';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { Lazy } from 'vs/base/common/lazy';
import { localize } from 'vs/nls';
import { Action2, MenuId } from 'vs/platform/actions/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions';
import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys';
export const ASK_QUICK_QUESTION_ACTION_ID = 'chat.action.askQuickQuestion';
export const enum QuickQuestionMode {
SingleQuestion = 'singleQuestion',
InputOnTopChat = 'inputOnTopChat',
InputOnBottomChat = 'inputOnBottomChat',
}
export interface IQuickQuestionMode {
run(accessor: ServicesAccessor, query: string): void;
}
// TODO: This should be registered per chat-provider probably.
export class AskQuickQuestionAction extends Action2 {
private static readonly modeRegistry: Map<QuickQuestionMode, Lazy<IQuickQuestionMode>> = new Map();
static registerMode(mode: QuickQuestionMode, modeAction: { new(): IQuickQuestionMode }) {
AskQuickQuestionAction.modeRegistry.set(mode, new Lazy(() => new modeAction()));
}
constructor() {
super({
id: ASK_QUICK_QUESTION_ACTION_ID,
title: { value: localize('chat', "Chat"), original: 'Chat' },
precondition: CONTEXT_PROVIDER_EXISTS,
icon: Codicon.commentDiscussion,
f1: false,
category: CHAT_CATEGORY,
keybinding: {
weight: KeybindingWeight.WorkbenchContrib,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyI,
linux: {
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyMod.Alt | KeyCode.KeyI
}
},
menu: {
id: MenuId.LayoutControlMenu,
group: '0_workbench_toggles',
when: ContextKeyExpr.notEquals('config.chat.experimental.defaultMode', 'chatView'),
order: 0
}
});
}
override run(accessor: ServicesAccessor, query: string): void {
const configurationService = accessor.get(IConfigurationService);
const mode = configurationService.getValue<QuickQuestionMode>('chat.experimental.quickQuestion.mode');
const modeAction = AskQuickQuestionAction.modeRegistry.get(mode);
if (modeAction) {
modeAction.value.run(accessor, query);
}
}
}
@@ -1,261 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as dom from 'vs/base/browser/dom';
import { Toggle } from 'vs/base/browser/ui/toggle/toggle';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Codicon } from 'vs/base/common/codicons';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { localize } from 'vs/nls';
import { Emitter, Event } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { asCssVariable, editorBackground, foreground, inputActiveOptionBackground, inputActiveOptionBorder, inputActiveOptionForeground } from 'vs/platform/theme/common/colorRegistry';
import { ChatListItemRenderer } from 'vs/workbench/contrib/chat/browser/chatListRenderer';
import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions';
import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { IChatReplyFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { ChatViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat';
import { AskQuickQuestionAction, IQuickQuestionMode, QuickQuestionMode } from 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction';
class AskSingleQuickQuestionMode implements IQuickQuestionMode {
private _currentSession: InteractiveQuickPickSession | undefined;
private _currentQuery: string | undefined;
private _lastAcceptedQuery: string | undefined;
private _currentTimer: any | undefined;
private _input: IQuickPick<IQuickPickItem> | undefined;
run(accessor: ServicesAccessor, query: string): void {
const quickInputService = accessor.get(IQuickInputService);
const chatService = accessor.get(IChatService);
const instantiationService = accessor.get(IInstantiationService);
// First things first, clear the existing timer that will dispose the session
clearTimeout(this._currentTimer);
this._currentTimer = undefined;
// If the input is already shown, hide it. This provides a toggle behavior of the quick pick
if (this._input !== undefined) {
this._input.hide();
return;
}
// Check if any providers are available. If not, show nothing
const providerInfo = chatService.getProviderInfos()[0];
if (!providerInfo) {
return;
}
const disposableStore = new DisposableStore();
//#region Setup quick pick
this._input = quickInputService.createQuickPick();
disposableStore.add(this._input);
this._input.placeholder = localize('askabot', "Ask {0} a question...", providerInfo.displayName);
// Setup toggle that will be used to open the chat view
const openInChat = new Toggle({
title: 'Open In Chat View',
icon: Codicon.commentDiscussion,
isChecked: false,
inputActiveOptionBorder: asCssVariable(inputActiveOptionBorder),
inputActiveOptionForeground: asCssVariable(inputActiveOptionForeground),
inputActiveOptionBackground: asCssVariable(inputActiveOptionBackground)
});
disposableStore.add(openInChat);
disposableStore.add(openInChat.onChange(async () => {
await this._currentSession?.openInChat(this._lastAcceptedQuery ?? this._input!.value);
this._currentQuery = undefined;
this._lastAcceptedQuery = undefined;
this._currentSession?.dispose();
this._currentSession = undefined;
}));
this._input.toggles = [openInChat];
// Setup the widget that will be used to render the chat response
const containerList = dom.$('.interactive-list');
const containerSession = dom.$('.interactive-session', undefined, containerList);
containerList.style.position = 'relative';
this._input.widget = containerSession;
//#endregion
//#region quick pick events
disposableStore.add(this._input.onDidChangeValue((value) => {
if (value !== this._currentQuery) {
this._currentQuery = value;
}
}));
disposableStore.add(this._input.onDidHide(() => {
disposableStore.dispose();
this._input = undefined;
this._currentTimer = setTimeout(() => {
this._currentQuery = undefined;
this._lastAcceptedQuery = undefined;
this._currentSession?.dispose();
this._currentSession = undefined;
}, 1000 * 30); // 30 seconds
}));
disposableStore.add(this._input.onDidAccept(async () => {
await this._currentSession?.accept(this._input!.value);
this._lastAcceptedQuery = this._input!.value;
}));
//#endregion
// If we were given a query (via executeCommand), then clear past state
if (query) {
this._currentSession?.dispose();
this._currentSession = undefined;
}
this._currentSession ??= instantiationService.createInstance(InteractiveQuickPickSession);
this._input.show();
// This line must come after showing the input so the offsetWidth is correct
this._currentSession.createList(containerList, containerList.offsetWidth);
disposableStore.add(this._currentSession.onDidClickFollowup(async e => {
this._input!.focusOnInput();
this._input!.value = e.message;
await this._currentSession?.accept(e.message);
}));
// If we were given a query (via executeCommand), then accept it
if (query) {
this._input.value = query;
this._input.valueSelection = [0, this._input.value.length];
this._currentQuery = query;
this._currentSession.accept(query);
} else if (this._currentQuery) {
this._input.value = this._currentQuery;
this._input.valueSelection = [0, this._input.value.length];
}
}
}
class InteractiveQuickPickSession extends Disposable {
private _model: ChatModel;
private _viewModel: ChatViewModel;
private readonly _onDidClickFollowup: Emitter<IChatReplyFollowup> = this._register(new Emitter<IChatReplyFollowup>());
onDidClickFollowup: Event<IChatReplyFollowup> = this._onDidClickFollowup.event;
private _listDisposable: DisposableStore | undefined;
constructor(
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IChatService private readonly _chatService: IChatService,
@IChatWidgetService private readonly _chatWidgetService: IChatWidgetService
) {
super();
const providerInfo = _chatService.getProviderInfos()[0];
this._model = this._register(_chatService.startSession(providerInfo.id, CancellationToken.None)!);
this._viewModel = this._register(new ChatViewModel(this._model, _instantiationService));
}
createList(container: HTMLElement, offsetWidth: number) {
this._listDisposable?.dispose();
this._listDisposable = new DisposableStore();
const options = this._listDisposable.add(this._instantiationService.createInstance(ChatEditorOptions, 'quickpick-interactive', foreground, editorBackground, editorBackground));
const list = this._listDisposable.add(this._instantiationService.createInstance(
ChatListItemRenderer,
options,
{
getListLength: () => {
return 1;
},
getSlashCommands() {
return [];
},
}
));
const template = list.renderTemplate(container);
list.layout(offsetWidth);
this._listDisposable.add(this._viewModel.onDidChange(() => {
const items = this._viewModel.getItems();
const node = {
element: items[items.length - 1],
children: [],
collapsed: false,
collapsible: false,
depth: 0,
filterData: undefined,
visible: true,
visibleChildIndex: 0,
visibleChildrenCount: 1,
};
list.disposeElement(node, 0, template);
list.renderElement(node, 0, template);
}));
if (this._viewModel.getItems().length) {
const items = this._viewModel.getItems();
const node = {
element: items[items.length - 1],
children: [],
collapsed: false,
collapsible: false,
depth: 0,
filterData: undefined,
visible: true,
visibleChildIndex: 0,
visibleChildrenCount: 1,
};
list.disposeElement(node, 0, template);
list.renderElement(node, 0, template);
}
this._listDisposable.add(list.onDidClickFollowup(e => {
this._onDidClickFollowup.fire(e);
}));
}
get providerId() {
return this._model.providerId;
}
get sessionId() {
return this._model.sessionId;
}
async accept(query: string) {
await this._model.waitForInitialization();
const requests = this._model.getRequests();
const lastRequest = requests[requests.length - 1];
if (lastRequest?.message && lastRequest?.message === query) {
return;
}
if (this._model.requestInProgress) {
this._chatService.cancelCurrentRequestForSession(this.sessionId);
}
await this._chatService.sendRequest(this.sessionId, query);
}
async openInChat(value: string) {
const widget = await this._chatWidgetService.revealViewForProvider(this.providerId);
if (!widget?.viewModel) {
return;
}
const requests = this._model.getRequests().reverse();
const response = requests.find(r => r.response?.response.value !== undefined);
const message = response?.response?.response.value;
if (message) {
this._chatService.addCompleteRequest(widget.viewModel.sessionId, value, { message });
} else if (value) {
this._chatService.sendRequest(widget.viewModel.sessionId, value);
}
widget.focusInput();
}
}
AskQuickQuestionAction.registerMode(QuickQuestionMode.SingleQuestion, AskSingleQuickQuestionMode);
@@ -42,7 +42,6 @@ import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel';
import { CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys';
import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chatAccessibilityService';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { QuickQuestionMode } from 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction';
import { alertFocusChange } from 'vs/workbench/contrib/accessibility/browser/accessibility.contribution';
import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration';
import { ChatWelcomeMessageModel } from 'vs/workbench/contrib/chat/common/chatModel';
@@ -92,13 +91,6 @@ configurationRegistry.registerConfiguration({
],
description: nls.localize('interactiveSession.defaultMode', "Controls the default mode of the chat experience."),
default: 'chatView'
},
'chat.experimental.quickQuestion.mode': {
type: 'string',
tags: ['experimental'],
enum: [QuickQuestionMode.SingleQuestion, QuickQuestionMode.InputOnTopChat, QuickQuestionMode.InputOnBottomChat],
description: nls.localize('interactiveSession.quickQuestion.mode', "Controls the mode of quick question chat experience."),
default: QuickQuestionMode.InputOnTopChat,
}
}
});
@@ -35,7 +35,7 @@ import { isFirefox } from 'vs/base/browser/browser';
import { IProductService } from 'vs/platform/product/common/productService';
import { ISemanticSimilarityService } from 'vs/workbench/services/semanticSimilarity/common/semanticSimilarityService';
import { IChatService } from 'vs/workbench/contrib/chat/common/chatService';
import { ASK_QUICK_QUESTION_ACTION_ID } from 'vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction';
import { ASK_QUICK_QUESTION_ACTION_ID } from 'vs/workbench/contrib/chat/browser/actions/chatQuickInputActions';
export class CommandsQuickAccessProvider extends AbstractEditorCommandsQuickAccessProvider {