mirror of
https://github.com/Microsoft/vscode
synced 2024-10-04 02:14:06 +00:00
adopt chat agent in terminal chat (#214499)
This commit is contained in:
parent
739a9e8579
commit
52c722c7bb
|
@ -448,12 +448,12 @@ export class InlineChatWidget {
|
|||
if (!viewModel) {
|
||||
return undefined;
|
||||
}
|
||||
for (const item of viewModel.getItems()) {
|
||||
if (isResponseVM(item)) {
|
||||
return viewModel.codeBlockModelCollection.get(viewModel.sessionId, item, codeBlockIndex)?.model;
|
||||
}
|
||||
const items = viewModel.getItems().filter(i => isResponseVM(i));
|
||||
if (!items.length) {
|
||||
return;
|
||||
}
|
||||
return undefined;
|
||||
const item = items[items.length - 1];
|
||||
return viewModel.codeBlockModelCollection.get(viewModel.sessionId, item, codeBlockIndex)?.model;
|
||||
}
|
||||
|
||||
get responseContent(): string | undefined {
|
||||
|
|
|
@ -15,9 +15,6 @@ export const enum TerminalChatCommandId {
|
|||
Discard = 'workbench.action.terminal.chat.discard',
|
||||
MakeRequest = 'workbench.action.terminal.chat.makeRequest',
|
||||
Cancel = 'workbench.action.terminal.chat.cancel',
|
||||
FeedbackHelpful = 'workbench.action.terminal.chat.feedbackHelpful',
|
||||
FeedbackUnhelpful = 'workbench.action.terminal.chat.feedbackUnhelpful',
|
||||
FeedbackReportIssue = 'workbench.action.terminal.chat.feedbackReportIssue',
|
||||
RunCommand = 'workbench.action.terminal.chat.runCommand',
|
||||
RunFirstCommand = 'workbench.action.terminal.chat.runFirstCommand',
|
||||
InsertCommand = 'workbench.action.terminal.chat.insertCommand',
|
||||
|
@ -30,7 +27,6 @@ export const enum TerminalChatCommandId {
|
|||
export const MENU_TERMINAL_CHAT_INPUT = MenuId.for('terminalChatInput');
|
||||
export const MENU_TERMINAL_CHAT_WIDGET = MenuId.for('terminalChatWidget');
|
||||
export const MENU_TERMINAL_CHAT_WIDGET_STATUS = MenuId.for('terminalChatWidget.status');
|
||||
export const MENU_TERMINAL_CHAT_WIDGET_FEEDBACK = MenuId.for('terminalChatWidget.feedback');
|
||||
export const MENU_TERMINAL_CHAT_WIDGET_TOOLBAR = MenuId.for('terminalChatWidget.toolbar');
|
||||
|
||||
export const enum TerminalChatContextKeyStrings {
|
||||
|
@ -61,18 +57,9 @@ export namespace TerminalChatContextKeys {
|
|||
/** Whether the chat input has text */
|
||||
export const inputHasText = new RawContextKey<boolean>(TerminalChatContextKeyStrings.ChatInputHasText, false, localize('chatInputHasTextContextKey', "Whether the chat input has text."));
|
||||
|
||||
/** Whether the terminal chat agent has been registered */
|
||||
export const agentRegistered = new RawContextKey<boolean>(TerminalChatContextKeyStrings.ChatAgentRegistered, false, localize('chatAgentRegisteredContextKey', "Whether the terminal chat agent has been registered."));
|
||||
|
||||
/** The chat response contains at least one code block */
|
||||
export const responseContainsCodeBlock = new RawContextKey<boolean>(TerminalChatContextKeyStrings.ChatResponseContainsCodeBlock, false, localize('chatResponseContainsCodeBlockContextKey', "Whether the chat response contains a code block."));
|
||||
|
||||
/** The chat response contains multiple code blocks */
|
||||
export const responseContainsMultipleCodeBlocks = new RawContextKey<boolean>(TerminalChatContextKeyStrings.ChatResponseContainsMultipleCodeBlocks, false, localize('chatResponseContainsMultipleCodeBlocksContextKey', "Whether the chat response contains multiple code blocks."));
|
||||
|
||||
/** Whether the response supports issue reporting */
|
||||
export const responseSupportsIssueReporting = new RawContextKey<boolean>(TerminalChatContextKeyStrings.ChatResponseSupportsIssueReporting, false, localize('chatResponseSupportsIssueReportingContextKey', "Whether the response supports issue reporting"));
|
||||
|
||||
/** The chat vote, if any for the response, if any */
|
||||
export const sessionResponseVote = new RawContextKey<string>(TerminalChatContextKeyStrings.ChatSessionResponseVote, undefined, { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") });
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import { CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_AGE
|
|||
import { isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions';
|
||||
import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey';
|
||||
import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId, TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat';
|
||||
import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId, TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat';
|
||||
import { TerminalChatController } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatController';
|
||||
|
||||
registerActiveXtermAction({
|
||||
|
@ -164,7 +164,6 @@ registerActiveXtermAction({
|
|||
precondition: ContextKeyExpr.and(
|
||||
ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated),
|
||||
TerminalChatContextKeys.requestActive.negate(),
|
||||
TerminalChatContextKeys.agentRegistered,
|
||||
TerminalChatContextKeys.responseContainsCodeBlock,
|
||||
TerminalChatContextKeys.responseContainsMultipleCodeBlocks.negate()
|
||||
),
|
||||
|
@ -196,7 +195,6 @@ registerActiveXtermAction({
|
|||
precondition: ContextKeyExpr.and(
|
||||
ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated),
|
||||
TerminalChatContextKeys.requestActive.negate(),
|
||||
TerminalChatContextKeys.agentRegistered,
|
||||
TerminalChatContextKeys.responseContainsMultipleCodeBlocks
|
||||
),
|
||||
icon: Codicon.play,
|
||||
|
@ -227,7 +225,6 @@ registerActiveXtermAction({
|
|||
precondition: ContextKeyExpr.and(
|
||||
ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated),
|
||||
TerminalChatContextKeys.requestActive.negate(),
|
||||
TerminalChatContextKeys.agentRegistered,
|
||||
TerminalChatContextKeys.responseContainsCodeBlock,
|
||||
TerminalChatContextKeys.responseContainsMultipleCodeBlocks.negate()
|
||||
),
|
||||
|
@ -259,7 +256,6 @@ registerActiveXtermAction({
|
|||
precondition: ContextKeyExpr.and(
|
||||
ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated),
|
||||
TerminalChatContextKeys.requestActive.negate(),
|
||||
TerminalChatContextKeys.agentRegistered,
|
||||
TerminalChatContextKeys.responseContainsMultipleCodeBlocks
|
||||
),
|
||||
keybinding: {
|
||||
|
@ -289,7 +285,6 @@ registerActiveXtermAction({
|
|||
precondition: ContextKeyExpr.and(
|
||||
ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated),
|
||||
TerminalChatContextKeys.requestActive.negate(),
|
||||
TerminalChatContextKeys.agentRegistered,
|
||||
),
|
||||
icon: Codicon.commentDiscussion,
|
||||
menu: [{
|
||||
|
@ -319,7 +314,6 @@ registerActiveXtermAction({
|
|||
precondition: ContextKeyExpr.and(
|
||||
ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated),
|
||||
TerminalChatContextKeys.requestActive.negate(),
|
||||
TerminalChatContextKeys.agentRegistered,
|
||||
CTX_INLINE_CHAT_EMPTY.negate()
|
||||
),
|
||||
icon: Codicon.send,
|
||||
|
@ -348,7 +342,6 @@ registerActiveXtermAction({
|
|||
title: localize2('cancelChat', 'Cancel Chat'),
|
||||
precondition: ContextKeyExpr.and(
|
||||
TerminalChatContextKeys.requestActive,
|
||||
TerminalChatContextKeys.agentRegistered
|
||||
),
|
||||
icon: Codicon.debugStop,
|
||||
menu: {
|
||||
|
@ -366,25 +359,39 @@ registerActiveXtermAction({
|
|||
});
|
||||
|
||||
registerActiveXtermAction({
|
||||
id: TerminalChatCommandId.FeedbackReportIssue,
|
||||
title: localize2('reportIssue', 'Report Issue'),
|
||||
precondition: ContextKeyExpr.and(
|
||||
TerminalChatContextKeys.requestActive.negate(),
|
||||
TerminalChatContextKeys.responseContainsCodeBlock.notEqualsTo(undefined),
|
||||
TerminalChatContextKeys.responseSupportsIssueReporting
|
||||
),
|
||||
icon: Codicon.report,
|
||||
menu: [{
|
||||
id: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK,
|
||||
when: ContextKeyExpr.and(TerminalChatContextKeys.responseContainsCodeBlock.notEqualsTo(undefined), TerminalChatContextKeys.responseSupportsIssueReporting),
|
||||
group: 'inline',
|
||||
order: 3
|
||||
}],
|
||||
id: TerminalChatCommandId.PreviousFromHistory,
|
||||
title: localize2('previousFromHitory', 'Previous From History'),
|
||||
precondition: TerminalChatContextKeys.focused,
|
||||
keybinding: {
|
||||
when: TerminalChatContextKeys.focused,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyCode.UpArrow,
|
||||
},
|
||||
|
||||
run: (_xterm, _accessor, activeInstance) => {
|
||||
if (isDetachedTerminalInstance(activeInstance)) {
|
||||
return;
|
||||
}
|
||||
const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance);
|
||||
contr?.acceptFeedback();
|
||||
contr?.populateHistory(true);
|
||||
}
|
||||
});
|
||||
|
||||
registerActiveXtermAction({
|
||||
id: TerminalChatCommandId.NextFromHistory,
|
||||
title: localize2('nextFromHitory', 'Next From History'),
|
||||
precondition: TerminalChatContextKeys.focused,
|
||||
keybinding: {
|
||||
when: TerminalChatContextKeys.focused,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyCode.DownArrow,
|
||||
},
|
||||
|
||||
run: (_xterm, _accessor, activeInstance) => {
|
||||
if (isDetachedTerminalInstance(activeInstance)) {
|
||||
return;
|
||||
}
|
||||
const contr = TerminalChatController.activeChatWidget || TerminalChatController.get(activeInstance);
|
||||
contr?.populateHistory(false);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -4,26 +4,27 @@
|
|||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import type { Terminal as RawXtermTerminal } from '@xterm/xterm';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Lazy } from 'vs/base/common/lazy';
|
||||
import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { GeneratingPhrase, IChatAccessibilityService, IChatCodeBlockContextProviderService, showChatView } from 'vs/workbench/contrib/chat/browser/chat';
|
||||
import { ChatAgentLocation, IChatAgentRequest, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
|
||||
import { ChatUserAction, IChatProgress, IChatService, ChatAgentVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { IChatCodeBlockContextProviderService, showChatView } from 'vs/workbench/contrib/chat/browser/chat';
|
||||
import { IChatProgress, IChatService } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager';
|
||||
import { ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { TerminalChatWidget } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChatWidget';
|
||||
|
||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { ChatModel, ChatRequestModel, IChatRequestVariableData, IChatResponseModel, getHistoryEntriesFromModel } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||
import { ChatModel, IChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel';
|
||||
import { TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat';
|
||||
import { IViewsService } from 'vs/workbench/services/views/common/viewsService';
|
||||
import { DeferredPromise } from 'vs/base/common/async';
|
||||
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
|
||||
import { assertType } from 'vs/base/common/types';
|
||||
import { CancelablePromise, createCancelablePromise, DeferredPromise } from 'vs/base/common/async';
|
||||
import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents';
|
||||
|
||||
const enum Message {
|
||||
NONE = 0,
|
||||
|
@ -48,6 +49,9 @@ export class TerminalChatController extends Disposable implements ITerminalContr
|
|||
*/
|
||||
static activeChatWidget?: TerminalChatController;
|
||||
|
||||
private static _storageKey = 'terminal-inline-chat-history';
|
||||
private static _promptHistory: string[] = [];
|
||||
|
||||
/**
|
||||
* The chat widget for the controller, this is lazy as we don't want to instantiate it until
|
||||
* both it's required and xterm is ready.
|
||||
|
@ -61,17 +65,11 @@ export class TerminalChatController extends Disposable implements ITerminalContr
|
|||
get chatWidget(): TerminalChatWidget | undefined { return this._chatWidget?.value; }
|
||||
|
||||
private readonly _requestActiveContextKey: IContextKey<boolean>;
|
||||
private readonly _terminalAgentRegisteredContextKey: IContextKey<boolean>;
|
||||
private readonly _responseContainsCodeBlockContextKey: IContextKey<boolean>;
|
||||
private readonly _responseContainsMulitpleCodeBlocksContextKey: IContextKey<boolean>;
|
||||
private readonly _responseSupportsIssueReportingContextKey: IContextKey<boolean>;
|
||||
private readonly _sessionResponseVoteContextKey: IContextKey<string | undefined>;
|
||||
|
||||
private _messages = this._store.add(new Emitter<Message>());
|
||||
|
||||
private _currentRequest: ChatRequestModel | undefined;
|
||||
|
||||
private _lastInput: string | undefined;
|
||||
private _lastResponseContent: string | undefined;
|
||||
get lastResponseContent(): string | undefined {
|
||||
return this._lastResponseContent;
|
||||
|
@ -81,7 +79,6 @@ export class TerminalChatController extends Disposable implements ITerminalContr
|
|||
get onDidHide() { return this.chatWidget?.onDidHide ?? Event.None; }
|
||||
|
||||
private _terminalAgentName = 'terminal';
|
||||
private _terminalAgentId: string | undefined;
|
||||
|
||||
private readonly _model: MutableDisposable<ChatModel> = this._register(new MutableDisposable());
|
||||
|
||||
|
@ -89,31 +86,32 @@ export class TerminalChatController extends Disposable implements ITerminalContr
|
|||
return this._chatWidget?.value.inlineChatWidget.scopedContextKeyService ?? this._contextKeyService;
|
||||
}
|
||||
|
||||
private _sessionCtor: CancelablePromise<void> | undefined;
|
||||
private _historyOffset: number = -1;
|
||||
private _historyCandidate: string = '';
|
||||
private _historyUpdate: (prompt: string) => void;
|
||||
|
||||
private _currentRequestId: string | undefined;
|
||||
private _activeRequestCts?: CancellationTokenSource;
|
||||
|
||||
constructor(
|
||||
private readonly _instance: ITerminalInstance,
|
||||
processManager: ITerminalProcessManager,
|
||||
widgetManager: TerminalWidgetManager,
|
||||
@ITerminalService private readonly _terminalService: ITerminalService,
|
||||
@IInstantiationService private readonly _instantiationService: IInstantiationService,
|
||||
@IChatAgentService private readonly _chatAgentService: IChatAgentService,
|
||||
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
|
||||
@IChatAccessibilityService private readonly _chatAccessibilityService: IChatAccessibilityService,
|
||||
@IChatService private readonly _chatService: IChatService,
|
||||
@IChatCodeBlockContextProviderService private readonly _chatCodeBlockContextProviderService: IChatCodeBlockContextProviderService,
|
||||
@IViewsService private readonly _viewsService: IViewsService,
|
||||
@IStorageService private readonly _storageService: IStorageService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._requestActiveContextKey = TerminalChatContextKeys.requestActive.bindTo(this._contextKeyService);
|
||||
this._terminalAgentRegisteredContextKey = TerminalChatContextKeys.agentRegistered.bindTo(this._contextKeyService);
|
||||
this._responseContainsCodeBlockContextKey = TerminalChatContextKeys.responseContainsCodeBlock.bindTo(this._contextKeyService);
|
||||
this._responseContainsMulitpleCodeBlocksContextKey = TerminalChatContextKeys.responseContainsMultipleCodeBlocks.bindTo(this._contextKeyService);
|
||||
this._responseSupportsIssueReportingContextKey = TerminalChatContextKeys.responseSupportsIssueReporting.bindTo(this._contextKeyService);
|
||||
this._sessionResponseVoteContextKey = TerminalChatContextKeys.sessionResponseVote.bindTo(this._contextKeyService);
|
||||
|
||||
if (!this.initTerminalAgent()) {
|
||||
this._register(this._chatAgentService.onDidChangeAgents(() => this.initTerminalAgent()));
|
||||
}
|
||||
this._register(this._chatCodeBlockContextProviderService.registerProvider({
|
||||
getCodeBlockContext: (editor) => {
|
||||
if (!editor || !this._chatWidget?.hasValue || !this.hasFocus()) {
|
||||
|
@ -128,34 +126,17 @@ export class TerminalChatController extends Disposable implements ITerminalContr
|
|||
}
|
||||
}, 'terminal'));
|
||||
|
||||
// TODO
|
||||
// This is glue/debt that's needed while ChatModel isn't yet adopted. The chat model uses
|
||||
// a default chat model (unless configured) and feedback is reported against that one. This
|
||||
// code forwards the feedback to an actual registered provider
|
||||
this._register(this._chatService.onDidPerformUserAction(e => {
|
||||
// only forward feedback from the inline chat widget default model
|
||||
if (
|
||||
this._chatWidget?.rawValue?.inlineChatWidget.usesDefaultChatModel
|
||||
&& e.sessionId === this._chatWidget?.rawValue?.inlineChatWidget.getChatModel().sessionId
|
||||
) {
|
||||
if (e.action.kind === 'bug') {
|
||||
this.acceptFeedback(undefined);
|
||||
} else if (e.action.kind === 'vote') {
|
||||
this.acceptFeedback(e.action.direction === ChatAgentVoteDirection.Up);
|
||||
}
|
||||
TerminalChatController._promptHistory = JSON.parse(this._storageService.get(TerminalChatController._storageKey, StorageScope.PROFILE, '[]'));
|
||||
this._historyUpdate = (prompt: string) => {
|
||||
const idx = TerminalChatController._promptHistory.indexOf(prompt);
|
||||
if (idx >= 0) {
|
||||
TerminalChatController._promptHistory.splice(idx, 1);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private initTerminalAgent(): boolean {
|
||||
const terminalAgent = this._chatAgentService.getAgentsByName(this._terminalAgentName)[0];
|
||||
if (terminalAgent) {
|
||||
this._terminalAgentId = terminalAgent.id;
|
||||
this._terminalAgentRegisteredContextKey.set(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
TerminalChatController._promptHistory.unshift(prompt);
|
||||
this._historyOffset = -1;
|
||||
this._historyCandidate = '';
|
||||
this._storageService.store(TerminalChatController._storageKey, JSON.stringify(TerminalChatController._promptHistory), StorageScope.PROFILE, StorageTarget.USER);
|
||||
};
|
||||
}
|
||||
|
||||
xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void {
|
||||
|
@ -178,41 +159,17 @@ export class TerminalChatController extends Disposable implements ITerminalContr
|
|||
});
|
||||
}
|
||||
|
||||
acceptFeedback(helpful?: boolean): void {
|
||||
const model = this._model.value;
|
||||
if (!this._currentRequest || !model) {
|
||||
return;
|
||||
}
|
||||
let action: ChatUserAction;
|
||||
if (helpful === undefined) {
|
||||
action = { kind: 'bug' };
|
||||
} else {
|
||||
this._sessionResponseVoteContextKey.set(helpful ? 'up' : 'down');
|
||||
action = { kind: 'vote', direction: helpful ? ChatAgentVoteDirection.Up : ChatAgentVoteDirection.Down };
|
||||
}
|
||||
// TODO:extract into helper method
|
||||
for (const request of model.getRequests()) {
|
||||
if (request.response?.response.value || request.response?.result) {
|
||||
this._chatService.notifyUserAction({
|
||||
sessionId: request.session.sessionId,
|
||||
requestId: request.id,
|
||||
agentId: request.response?.agent?.id,
|
||||
result: request.response?.result,
|
||||
action
|
||||
});
|
||||
}
|
||||
}
|
||||
this._chatWidget?.value.inlineChatWidget.updateStatus('Thank you for your feedback!', { resetAfter: 1250 });
|
||||
}
|
||||
private async _createSession(): Promise<void> {
|
||||
this._sessionCtor = createCancelablePromise<void>(async token => {
|
||||
if (!this._model.value) {
|
||||
this._model.value = this._chatService.startSession(ChatAgentLocation.Terminal, token);
|
||||
|
||||
cancel(): void {
|
||||
if (this._currentRequest) {
|
||||
this._model.value?.cancelRequest(this._currentRequest);
|
||||
}
|
||||
this._requestActiveContextKey.set(false);
|
||||
this._chatWidget?.value.inlineChatWidget.updateProgress(false);
|
||||
this._chatWidget?.value.inlineChatWidget.updateInfo('');
|
||||
this._chatWidget?.value.inlineChatWidget.updateToolbar(true);
|
||||
if (!this._model.value) {
|
||||
throw new Error('Failed to start chat session');
|
||||
}
|
||||
}
|
||||
});
|
||||
this._register(toDisposable(() => this._sessionCtor?.cancel()));
|
||||
}
|
||||
|
||||
private _forcedPlaceholder: string | undefined = undefined;
|
||||
|
@ -239,112 +196,65 @@ export class TerminalChatController extends Disposable implements ITerminalContr
|
|||
}
|
||||
|
||||
clear(): void {
|
||||
if (this._currentRequest) {
|
||||
this._model.value?.cancelRequest(this._currentRequest);
|
||||
}
|
||||
this.cancel();
|
||||
this._model.clear();
|
||||
this._chatWidget?.rawValue?.hide();
|
||||
this._chatWidget?.rawValue?.setValue(undefined);
|
||||
this._responseContainsCodeBlockContextKey.reset();
|
||||
this._sessionResponseVoteContextKey.reset();
|
||||
this._requestActiveContextKey.reset();
|
||||
this._chatWidget?.value.hide();
|
||||
this._chatWidget?.value.setValue(undefined);
|
||||
}
|
||||
|
||||
async acceptInput(): Promise<IChatResponseModel | undefined> {
|
||||
if (!this._model.value) {
|
||||
this._model.value = this._chatService.startSession(ChatAgentLocation.Terminal, CancellationToken.None);
|
||||
if (!this._model.value) {
|
||||
throw new Error('Could not start chat session');
|
||||
}
|
||||
}
|
||||
this._messages.fire(Message.ACCEPT_INPUT);
|
||||
const model = this._model.value;
|
||||
|
||||
this._lastInput = this._chatWidget?.value?.input();
|
||||
if (!this._lastInput) {
|
||||
assertType(this._chatWidget);
|
||||
assertType(this._model.value);
|
||||
const lastInput = this._chatWidget.value.inlineChatWidget.value;
|
||||
if (!lastInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
const responseCreated = new DeferredPromise<IChatResponseModel>();
|
||||
let responseCreatedComplete = false;
|
||||
const completeResponseCreated = () => {
|
||||
if (!responseCreatedComplete && this._currentRequest?.response) {
|
||||
responseCreated.complete(this._currentRequest.response);
|
||||
responseCreatedComplete = true;
|
||||
}
|
||||
};
|
||||
|
||||
const accessibilityRequestId = this._chatAccessibilityService.acceptRequest();
|
||||
const model = this._model.value;
|
||||
this._chatWidget.value.inlineChatWidget.setChatModel(model);
|
||||
this._historyUpdate(lastInput);
|
||||
this._activeRequestCts?.cancel();
|
||||
this._activeRequestCts = new CancellationTokenSource();
|
||||
const store = new DisposableStore();
|
||||
this._requestActiveContextKey.set(true);
|
||||
const cancellationToken = new CancellationTokenSource().token;
|
||||
let responseContent = '';
|
||||
const progressCallback = (progress: IChatProgress) => {
|
||||
if (cancellationToken.isCancellationRequested) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (progress.kind === 'markdownContent') {
|
||||
responseContent += progress.content.value;
|
||||
}
|
||||
if (this._currentRequest) {
|
||||
model.acceptResponseProgress(this._currentRequest, progress);
|
||||
completeResponseCreated();
|
||||
}
|
||||
};
|
||||
|
||||
await model.waitForInitialization();
|
||||
this._chatWidget?.value.addToHistory(this._lastInput);
|
||||
const request: IParsedChatRequest = {
|
||||
text: this._lastInput,
|
||||
parts: []
|
||||
};
|
||||
const requestVarData: IChatRequestVariableData = {
|
||||
variables: []
|
||||
};
|
||||
this._currentRequest = model.addRequest(request, requestVarData, 0);
|
||||
completeResponseCreated();
|
||||
const requestProps: IChatAgentRequest = {
|
||||
sessionId: model.sessionId,
|
||||
requestId: this._currentRequest!.id,
|
||||
agentId: this._terminalAgentId!,
|
||||
message: this._lastInput,
|
||||
variables: { variables: [] },
|
||||
location: ChatAgentLocation.Terminal
|
||||
};
|
||||
const response = await this._chatWidget.value.inlineChatWidget.chatWidget.acceptInput(lastInput);
|
||||
this._currentRequestId = response?.requestId;
|
||||
const responsePromise = new DeferredPromise<IChatResponseModel | undefined>();
|
||||
try {
|
||||
const task = this._chatAgentService.invokeAgent(this._terminalAgentId!, requestProps, progressCallback, getHistoryEntriesFromModel(model, this._terminalAgentId!), cancellationToken);
|
||||
this._chatWidget?.value.inlineChatWidget.updateChatMessage(undefined);
|
||||
this._chatWidget?.value.inlineChatWidget.updateProgress(true);
|
||||
this._chatWidget?.value.inlineChatWidget.updateInfo(GeneratingPhrase + '\u2026');
|
||||
await task;
|
||||
} catch (e) {
|
||||
|
||||
this._requestActiveContextKey.set(true);
|
||||
if (response) {
|
||||
store.add(response.onDidChange(async () => {
|
||||
responseContent += response.response.value;
|
||||
this._chatWidget?.value.inlineChatWidget.updateProgress(true);
|
||||
if (response.isCanceled) {
|
||||
this._requestActiveContextKey.set(false);
|
||||
responsePromise.complete(undefined);
|
||||
return;
|
||||
}
|
||||
if (response.isComplete) {
|
||||
this._requestActiveContextKey.set(false);
|
||||
this._requestActiveContextKey.set(false);
|
||||
const containsCode = responseContent.includes('```');
|
||||
this._chatWidget!.value.inlineChatWidget.updateChatMessage({ message: new MarkdownString(responseContent), requestId: response!.requestId }, false, containsCode);
|
||||
const firstCodeBlock = await this.chatWidget?.inlineChatWidget.getCodeBlockInfo(0);
|
||||
const secondCodeBlock = await this.chatWidget?.inlineChatWidget.getCodeBlockInfo(1);
|
||||
this._responseContainsCodeBlockContextKey.set(!!firstCodeBlock);
|
||||
this._responseContainsMulitpleCodeBlocksContextKey.set(!!secondCodeBlock);
|
||||
this._chatWidget?.value.inlineChatWidget.updateToolbar(true);
|
||||
this._chatWidget?.value.inlineChatWidget.updateProgress(false);
|
||||
responsePromise.complete(response);
|
||||
}
|
||||
}));
|
||||
}
|
||||
await responsePromise.p;
|
||||
return response;
|
||||
} catch {
|
||||
return;
|
||||
} finally {
|
||||
this._requestActiveContextKey.set(false);
|
||||
this._chatWidget?.value.inlineChatWidget.updateProgress(false);
|
||||
this._chatWidget?.value.inlineChatWidget.updateInfo('');
|
||||
this._chatWidget?.value.inlineChatWidget.updateToolbar(true);
|
||||
if (this._currentRequest) {
|
||||
model.completeResponse(this._currentRequest);
|
||||
completeResponseCreated();
|
||||
}
|
||||
this._lastResponseContent = responseContent;
|
||||
if (this._currentRequest) {
|
||||
this._chatAccessibilityService.acceptResponse(responseContent, accessibilityRequestId);
|
||||
const containsCode = responseContent.includes('```');
|
||||
this._chatWidget?.value.inlineChatWidget.updateChatMessage({ message: new MarkdownString(responseContent), requestId: this._currentRequest.id }, false, containsCode);
|
||||
const firstCodeBlock = await this.chatWidget?.inlineChatWidget.getCodeBlockInfo(0);
|
||||
const secondCodeBlock = await this.chatWidget?.inlineChatWidget.getCodeBlockInfo(1);
|
||||
this._responseContainsCodeBlockContextKey.set(!!firstCodeBlock);
|
||||
this._responseContainsMulitpleCodeBlocksContextKey.set(!!secondCodeBlock);
|
||||
this._chatWidget?.value.inlineChatWidget.updateToolbar(true);
|
||||
}
|
||||
const supportIssueReporting = this._currentRequest?.response?.agent?.metadata?.supportIssueReporting;
|
||||
if (supportIssueReporting !== undefined) {
|
||||
this._responseSupportsIssueReportingContextKey.set(supportIssueReporting);
|
||||
}
|
||||
store.dispose();
|
||||
}
|
||||
return responseCreated.p;
|
||||
}
|
||||
|
||||
updateInput(text: string, selectAll = true): void {
|
||||
|
@ -369,6 +279,47 @@ export class TerminalChatController extends Disposable implements ITerminalContr
|
|||
return !!this._chatWidget?.rawValue?.hasFocus() ?? false;
|
||||
}
|
||||
|
||||
populateHistory(up: boolean) {
|
||||
if (!this._chatWidget?.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
const len = TerminalChatController._promptHistory.length;
|
||||
if (len === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._historyOffset === -1) {
|
||||
// remember the current value
|
||||
this._historyCandidate = this._chatWidget.value.inlineChatWidget.value;
|
||||
}
|
||||
|
||||
const newIdx = this._historyOffset + (up ? 1 : -1);
|
||||
if (newIdx >= len) {
|
||||
// reached the end
|
||||
return;
|
||||
}
|
||||
|
||||
let entry: string;
|
||||
if (newIdx < 0) {
|
||||
entry = this._historyCandidate;
|
||||
this._historyOffset = -1;
|
||||
} else {
|
||||
entry = TerminalChatController._promptHistory[newIdx];
|
||||
this._historyOffset = newIdx;
|
||||
}
|
||||
|
||||
this._chatWidget.value.inlineChatWidget.value = entry;
|
||||
this._chatWidget.value.inlineChatWidget.selectAll();
|
||||
}
|
||||
|
||||
cancel(): void {
|
||||
this._sessionCtor?.cancel();
|
||||
this._sessionCtor = undefined;
|
||||
this._activeRequestCts?.cancel();
|
||||
this._requestActiveContextKey.set(false);
|
||||
}
|
||||
|
||||
async acceptCommand(shouldExecute: boolean): Promise<void> {
|
||||
const code = await this.chatWidget?.inlineChatWidget.getCodeBlockInfo(0);
|
||||
if (!code) {
|
||||
|
@ -377,18 +328,22 @@ export class TerminalChatController extends Disposable implements ITerminalContr
|
|||
this._chatWidget?.value.acceptCommand(code.textEditorModel.getValue(), shouldExecute);
|
||||
}
|
||||
|
||||
reveal(): void {
|
||||
async reveal(): Promise<void> {
|
||||
await this._createSession();
|
||||
this._chatWidget?.value.reveal();
|
||||
this._chatWidget?.value.focus();
|
||||
}
|
||||
|
||||
async viewInChat(): Promise<void> {
|
||||
//TODO: is this necessary? better way?
|
||||
const widget = await showChatView(this._viewsService);
|
||||
const request = this._currentRequest;
|
||||
if (!widget || !request?.response) {
|
||||
const currentRequest = this.chatWidget?.inlineChatWidget.chatWidget.viewModel?.model.getRequests().find(r => r.id === this._currentRequestId);
|
||||
if (!widget || !currentRequest?.response) {
|
||||
return;
|
||||
}
|
||||
|
||||
const message: IChatProgress[] = [];
|
||||
for (const item of request.response.response.value) {
|
||||
for (const item of currentRequest.response.response.value) {
|
||||
if (item.kind === 'textEditGroup') {
|
||||
for (const group of item.edits) {
|
||||
message.push({
|
||||
|
@ -404,24 +359,15 @@ export class TerminalChatController extends Disposable implements ITerminalContr
|
|||
|
||||
this._chatService.addCompleteRequest(widget!.viewModel!.sessionId,
|
||||
// DEBT: Add hardcoded agent name until its removed
|
||||
`@${this._terminalAgentName} ${request.message.text}`,
|
||||
request.variableData,
|
||||
request.attempt,
|
||||
`@${this._terminalAgentName} ${currentRequest.message.text}`,
|
||||
currentRequest.variableData,
|
||||
currentRequest.attempt,
|
||||
{
|
||||
message,
|
||||
result: request.response!.result,
|
||||
followups: request.response!.followups
|
||||
result: currentRequest.response!.result,
|
||||
followups: currentRequest.response!.followups
|
||||
});
|
||||
widget.focusLastMessage();
|
||||
this._chatWidget?.rawValue?.hide();
|
||||
}
|
||||
|
||||
// TODO: Move to register calls, don't override
|
||||
override dispose() {
|
||||
if (this._currentRequest) {
|
||||
this._model.value?.cancelRequest(this._currentRequest);
|
||||
}
|
||||
super.dispose();
|
||||
this.clear();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ import { ChatAgentLocation } from 'vs/workbench/contrib/chat/common/chatAgents';
|
|||
import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService';
|
||||
import { InlineChatWidget } from 'vs/workbench/contrib/inlineChat/browser/inlineChatWidget';
|
||||
import { ITerminalInstance, type IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal';
|
||||
import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_FEEDBACK, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId, TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat';
|
||||
import { MENU_TERMINAL_CHAT_INPUT, MENU_TERMINAL_CHAT_WIDGET, MENU_TERMINAL_CHAT_WIDGET_STATUS, TerminalChatCommandId, TerminalChatContextKeys } from 'vs/workbench/contrib/terminalContrib/chat/browser/terminalChat';
|
||||
import { TerminalStickyScrollContribution } from 'vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollContribution';
|
||||
|
||||
const enum Constants {
|
||||
|
@ -72,7 +72,6 @@ export class TerminalChatWidget extends Disposable {
|
|||
}
|
||||
}
|
||||
},
|
||||
feedbackMenuId: MENU_TERMINAL_CHAT_WIDGET_FEEDBACK,
|
||||
telemetrySource: 'terminal-inline-chat',
|
||||
rendererOptions: { editableCodeBlock: true }
|
||||
}
|
||||
|
@ -80,6 +79,7 @@ export class TerminalChatWidget extends Disposable {
|
|||
this._register(Event.any(
|
||||
this._inlineChatWidget.onDidChangeHeight,
|
||||
this._instance.onDimensionsChanged,
|
||||
this._inlineChatWidget.chatWidget.onDidChangeContentHeight,
|
||||
Event.debounce(this._xterm.raw.onCursorMove, () => void 0, MicrotaskDelay),
|
||||
)(() => this._relayout()));
|
||||
|
||||
|
@ -155,11 +155,15 @@ export class TerminalChatWidget extends Disposable {
|
|||
if (!terminalWrapperHeight) {
|
||||
return;
|
||||
}
|
||||
if (top > terminalWrapperHeight - widgetHeight) {
|
||||
if (top > terminalWrapperHeight - widgetHeight && terminalWrapperHeight - widgetHeight > 0) {
|
||||
this._setTerminalOffset(top - (terminalWrapperHeight - widgetHeight));
|
||||
} else {
|
||||
this._setTerminalOffset(undefined);
|
||||
}
|
||||
if (terminalWrapperHeight - widgetHeight < 0) {
|
||||
this._dimension = new Dimension(this._dimension!.width, terminalWrapperHeight - top - 20);
|
||||
this._inlineChatWidget.layout(this._dimension!);
|
||||
}
|
||||
}
|
||||
|
||||
private _getTerminalWrapperHeight(): number | undefined {
|
||||
|
|
Loading…
Reference in a new issue