diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts index e03b39894f3..0ea789e3602 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts @@ -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 | 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 { + if (this.widget.inputEditor.getValue().trim() === '/clear') { + this.clear(); + } else { + await this.widget.acceptInput(); + } + } + + async openChatView(): Promise { + 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 }); + } +} diff --git a/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/multipleByScrollQuickQuestionAction.ts b/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/multipleByScrollQuickQuestionAction.ts deleted file mode 100644 index b7357eb1366..00000000000 --- a/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/multipleByScrollQuickQuestionAction.ts +++ /dev/null @@ -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 | 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 { - if (this.widget.inputEditor.getValue().trim() === '/clear') { - this.clear(); - } else { - await this.widget.acceptInput(); - } - this.layout(); - } - - async openChatView(): Promise { - 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 - }); - } - } -); diff --git a/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction.ts b/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction.ts deleted file mode 100644 index a76eb9f6636..00000000000 --- a/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/quickQuestionAction.ts +++ /dev/null @@ -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> = 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('chat.experimental.quickQuestion.mode'); - const modeAction = AskQuickQuestionAction.modeRegistry.get(mode); - if (modeAction) { - modeAction.value.run(accessor, query); - } - } -} diff --git a/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/singleQuickQuestionAction.ts b/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/singleQuickQuestionAction.ts deleted file mode 100644 index 606a67c79f0..00000000000 --- a/src/vs/workbench/contrib/chat/browser/actions/quickQuestionActions/singleQuickQuestionAction.ts +++ /dev/null @@ -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 | 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 = this._register(new Emitter()); - onDidClickFollowup: Event = 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); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index cb842efe9bd..bb72115ff3a 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -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, } } }); diff --git a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts index 328d2745024..dd837a29ec7 100644 --- a/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts +++ b/src/vs/workbench/contrib/quickaccess/browser/commandsQuickAccess.ts @@ -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 {