Skip to content

Commit

Permalink
Hook up feedback/user-action events for inline chat participants (#21…
Browse files Browse the repository at this point in the history
…0040)

- add a special user-action
- inline chat controller doesn't call handleFeedback directly anymore
- ReplyResonse knows its chat model response
  • Loading branch information
jrieken authored Apr 10, 2024
1 parent 13a88a8 commit 6a06de0
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 43 deletions.
2 changes: 2 additions & 0 deletions src/vs/workbench/api/common/extHostTypeConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2697,6 +2697,8 @@ export namespace ChatAgentUserActionEvent {
} else if (event.action.kind === 'followUp') {
const followupAction: vscode.ChatFollowupAction = { kind: 'followUp', followup: ChatFollowup.to(event.action.followup) };
return { action: followupAction, result: ehResult };
} else if (event.action.kind === 'inlineChat') {
return { action: { kind: 'editor', accepted: event.action.action === 'accepted' }, result: ehResult };
} else {
return { action: event.action, result: ehResult };
}
Expand Down
7 changes: 6 additions & 1 deletion src/vs/workbench/contrib/chat/common/chatService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,12 @@ export interface IChatBugReportAction {
kind: 'bug';
}

export type ChatUserAction = IChatVoteAction | IChatCopyAction | IChatInsertAction | IChatTerminalAction | IChatCommandAction | IChatFollowupAction | IChatBugReportAction;
export interface IChatInlineChatCodeAction {
kind: 'inlineChat';
action: 'accepted' | 'discarded';
}

export type ChatUserAction = IChatVoteAction | IChatCopyAction | IChatInsertAction | IChatTerminalAction | IChatCommandAction | IChatFollowupAction | IChatBugReportAction | IChatInlineChatCodeAction;

export interface IChatUserActionEvent {
action: ChatUserAction;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/em
import { EmbeddedCodeEditorWidget } from 'vs/editor/browser/widget/codeEditor/embeddedCodeEditorWidget';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { InlineChatController, InlineChatRunOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController';
import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET_DISCARD, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_EDIT_MODE, EditMode, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, ACTION_ACCEPT_CHANGES, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, InlineChatResponseFeedbackKind, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, MENU_INLINE_CHAT_WIDGET, ACTION_TOGGLE_DIFF } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_HAS_PROVIDER, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET_DISCARD, MENU_INLINE_CHAT_WIDGET_STATUS, CTX_INLINE_CHAT_EDIT_MODE, EditMode, CTX_INLINE_CHAT_DOCUMENT_CHANGED, CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_HAS_STASHED_SESSION, ACTION_ACCEPT_CHANGES, CTX_INLINE_CHAT_RESPONSE_TYPES, InlineChatResponseTypes, ACTION_VIEW_IN_CHAT, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_RESPONSE_FOCUSED, CTX_INLINE_CHAT_CHANGE_SHOWS_DIFF, CTX_INLINE_CHAT_CHANGE_HAS_DIFF, MENU_INLINE_CHAT_WIDGET, ACTION_TOGGLE_DIFF, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { localize, localize2 } from 'vs/nls';
import { Action2, IAction2Options, MenuRegistry } from 'vs/platform/actions/common/actions';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
Expand Down Expand Up @@ -388,7 +388,7 @@ export class ReportIssueForBugCommand extends AbstractInlineChatAction {
}

override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController): void {
ctrl.feedbackLast(InlineChatResponseFeedbackKind.Bug);
ctrl.reportBug();
}
}

Expand Down
53 changes: 35 additions & 18 deletions src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ import { EmptyResponse, ErrorResponse, ReplyResponse, Session, SessionPrompt } f
import { IInlineChatSessionService } from './inlineChatSessionService';
import { EditModeStrategy, IEditObserver, LiveStrategy, PreviewStrategy, ProgressingEditsOptions } from 'vs/workbench/contrib/inlineChat/browser/inlineChatStrategies';
import { InlineChatZoneWidget } from './inlineChatZoneWidget';
import { CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_RESPONSE_TYPES, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_VISIBLE, EditMode, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseFeedbackKind, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { CTX_INLINE_CHAT_DID_EDIT, CTX_INLINE_CHAT_LAST_FEEDBACK, CTX_INLINE_CHAT_RESPONSE_TYPES, CTX_INLINE_CHAT_SUPPORT_ISSUE_REPORTING, CTX_INLINE_CHAT_USER_DID_EDIT, CTX_INLINE_CHAT_VISIBLE, EditMode, INLINE_CHAT_ID, InlineChatConfigKeys, InlineChatResponseTypes } from 'vs/workbench/contrib/inlineChat/common/inlineChat';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { StashedSession } from './inlineChatSession';
import { IModelDeltaDecoration, ITextModel, IValidEditOperation } from 'vs/editor/common/model';
Expand Down Expand Up @@ -1108,19 +1108,16 @@ export class InlineChatController implements IEditorContribution {
this._strategy?.toggleDiff?.();
}

feedbackLast(kind: InlineChatResponseFeedbackKind) {
if (this._session?.lastExchange?.response instanceof ReplyResponse) {
this._session.provider.handleInlineChatResponseFeedback?.(this._session.session, this._session.lastExchange.response.raw, kind);
switch (kind) {
case InlineChatResponseFeedbackKind.Helpful:
this._ctxLastFeedbackKind.set('helpful');
break;
case InlineChatResponseFeedbackKind.Unhelpful:
this._ctxLastFeedbackKind.set('unhelpful');
break;
default:
break;
}
reportBug() {
if (this._session?.lastExchange?.response instanceof ReplyResponse && this._session?.lastExchange?.response.chatResponse) {
const response = this._session.lastExchange.response.chatResponse;
this._chatService.notifyUserAction({
sessionId: response.session.sessionId,
requestId: response.requestId,
agentId: response.agent?.id,
result: response.result,
action: { kind: 'bug' }
});
this._zone.value.widget.updateStatus('Thank you for your feedback!', { resetAfter: 1250 });
}
}
Expand All @@ -1132,8 +1129,18 @@ export class InlineChatController implements IEditorContribution {
}

acceptSession(): void {
if (this._session?.lastExchange?.response instanceof ReplyResponse) {
this._session.provider.handleInlineChatResponseFeedback?.(this._session.session, this._session.lastExchange.response.raw, InlineChatResponseFeedbackKind.Accepted);
if (this._session?.lastExchange?.response instanceof ReplyResponse && this._session?.lastExchange?.response.chatResponse) {
const response = this._session?.lastExchange?.response.chatResponse;
this._chatService.notifyUserAction({
sessionId: response.session.sessionId,
requestId: response.requestId,
agentId: response.agent?.id,
result: response.result,
action: {
kind: 'inlineChat',
action: 'accepted'
}
});
}
this._messages.fire(Message.ACCEPT_SESSION);
}
Expand All @@ -1154,8 +1161,18 @@ export class InlineChatController implements IEditorContribution {
const diff = await this._editorWorkerService.computeDiff(this._session.textModel0.uri, this._session.textModelN.uri, { ignoreTrimWhitespace: false, maxComputationTimeMs: 5000, computeMoves: false }, 'advanced');
result = this._session.asChangedText(diff?.changes ?? []);

if (this._session.lastExchange?.response instanceof ReplyResponse) {
this._session.provider.handleInlineChatResponseFeedback?.(this._session.session, this._session.lastExchange.response.raw, InlineChatResponseFeedbackKind.Undone);
if (this._session.lastExchange?.response instanceof ReplyResponse && this._session?.lastExchange?.response.chatResponse) {
const response = this._session?.lastExchange?.response.chatResponse;
this._chatService.notifyUserAction({
sessionId: response.session.sessionId,
requestId: response.requestId,
agentId: response.agent?.id,
result: response.result,
action: {
kind: 'inlineChat',
action: 'discarded'
}
});
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ILogService } from 'vs/platform/log/common/log';
import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { ChatModel, IChatResponseModel } from 'vs/workbench/contrib/chat/common/chatModel';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';


Expand Down Expand Up @@ -323,6 +323,7 @@ export class ReplyResponse {
readonly modelAltVersionId: number,
progressEdits: TextEdit[][],
readonly requestId: string,
readonly chatResponse: IChatResponseModel | undefined,
@ITextFileService private readonly _textFileService: ITextFileService,
@ILanguageService private readonly _languageService: ILanguageService,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class BridgeAgent implements IChatAgentImplementation {
return data;
}

async invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult> {
async invoke(request: IChatAgentRequest, progress: (part: IChatProgress) => void, _history: IChatAgentHistoryEntry[], token: CancellationToken): Promise<IChatAgentResult> {

if (token.isCancellationRequested) {
return {};
Expand Down Expand Up @@ -129,7 +129,9 @@ class BridgeAgent implements IChatAgentImplementation {

const markdownContents = result.message ?? new MarkdownString('', { supportThemeIcons: true, supportHtml: true, isTrusted: false });

response = this._instaService.createInstance(ReplyResponse, result, markdownContents, session.textModelN.uri, modelAltVersionIdNow, progressEdits, request.requestId);
const chatModelRequest = session.chatModel.getRequests().find(candidate => candidate.id === request.requestId);

response = this._instaService.createInstance(ReplyResponse, result, markdownContents, session.textModelN.uri, modelAltVersionIdNow, progressEdits, request.requestId, chatModelRequest?.response);

} else {
response = new EmptyResponse();
Expand All @@ -141,9 +143,6 @@ class BridgeAgent implements IChatAgentImplementation {

this._postLastResponse({ id: request.requestId, response });

// TODO@jrieken
// result?.placeholder
// result?.wholeRange

return {
metadata: {
Expand Down Expand Up @@ -315,18 +314,19 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
}

if (otherEditorAgent) {
brigdeAgent.clear();
_logService.debug(`REMOVED bridge agent "${agentData.id}", found "${otherEditorAgent.id}"`);
bridgeStore.clear();
_logService.info(`REMOVED bridge agent "${agentData.id}", found "${otherEditorAgent.id}"`);

This comment has been minimized.

Copy link
@bpasero

bpasero Apr 11, 2024

Member

trace maybe better here?


} else if (!myEditorAgent) {
brigdeAgent.value = this._chatAgentService.registerDynamicAgent(agentData, this._instaService.createInstance(BridgeAgent, agentData, this._sessions, data => {
bridgeStore.value = this._chatAgentService.registerDynamicAgent(agentData, this._instaService.createInstance(BridgeAgent, agentData, this._sessions, data => {
this._lastResponsesFromBridgeAgent.set(data.id, data.response);
}));
_logService.debug(`ADDED bridge agent "${agentData.id}"`);
_logService.info(`ADDED bridge agent "${agentData.id}"`);
}
};

this._store.add(this._chatAgentService.onDidChangeAgents(() => addOrRemoveBridgeAgent()));
const brigdeAgent = this._store.add(new MutableDisposable());
const bridgeStore = this._store.add(new MutableDisposable());
addOrRemoveBridgeAgent();


Expand Down Expand Up @@ -465,7 +465,8 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
session.textModelN.uri,
modelAltVersionIdNow,
[],
e.request.id
e.request.id,
e.request.response
);
}
}
Expand All @@ -475,20 +476,32 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
}));

store.add(this._chatService.onDidPerformUserAction(e => {
if (e.sessionId !== chatModel.sessionId || e.action.kind !== 'vote') {
if (e.sessionId !== chatModel.sessionId) {
return;
}

// TODO@jrieken VALIDATE candidate is proper, e.g check with `session.exchanges`
const request = chatModel.getRequests().find(request => request.id === e.requestId);
const candidate = request?.response?.result?.metadata?.inlineChatResponse;
if (candidate) {
provider.handleInlineChatResponseFeedback?.(
rawSession,
candidate,
e.action.direction === InteractiveSessionVoteDirection.Down ? InlineChatResponseFeedbackKind.Unhelpful : InlineChatResponseFeedbackKind.Helpful
);

if (!candidate) {
return;
}

let kind: InlineChatResponseFeedbackKind | undefined;
if (e.action.kind === 'vote') {
kind = e.action.direction === InteractiveSessionVoteDirection.Down ? InlineChatResponseFeedbackKind.Unhelpful : InlineChatResponseFeedbackKind.Helpful;
} else if (e.action.kind === 'bug') {
kind = InlineChatResponseFeedbackKind.Bug;
} else if (e.action.kind === 'inlineChat') {
kind = e.action.action === 'accepted' ? InlineChatResponseFeedbackKind.Accepted : InlineChatResponseFeedbackKind.Undone;
}

if (!kind) {
return;
}

provider.handleInlineChatResponseFeedback?.(rawSession, candidate, kind);
}));

store.add(this._inlineChatService.onDidChangeProviders(e => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -644,7 +644,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito
response = new EmptyResponse();
} else {
const markdownContents = new MarkdownString('', { supportThemeIcons: true, supportHtml: true, isTrusted: false });
const replyResponse = response = this._instantiationService.createInstance(ReplyResponse, reply, markdownContents, this._activeSession.textModelN.uri, this._activeSession.textModelN.getAlternativeVersionId(), progressEdits, request.requestId);
const replyResponse = response = this._instantiationService.createInstance(ReplyResponse, reply, markdownContents, this._activeSession.textModelN.uri, this._activeSession.textModelN.getAlternativeVersionId(), progressEdits, request.requestId, undefined);
for (let i = progressEdits.length; i < replyResponse.allLocalEdits.length; i++) {
await this._makeChanges(replyResponse.allLocalEdits[i], undefined);
}
Expand Down Expand Up @@ -747,7 +747,7 @@ export class NotebookChatController extends Disposable implements INotebookEdito
}

const markdownContents = new MarkdownString('', { supportThemeIcons: true, supportHtml: true, isTrusted: false });
const response = this._instantiationService.createInstance(ReplyResponse, reply, markdownContents, this._activeSession.textModelN.uri, this._activeSession.textModelN.getAlternativeVersionId(), [], request.requestId);
const response = this._instantiationService.createInstance(ReplyResponse, reply, markdownContents, this._activeSession.textModelN.uri, this._activeSession.textModelN.getAlternativeVersionId(), [], request.requestId, undefined);
const followups = await this._activeSession.provider.provideFollowups(this._activeSession.session, response.raw, token);
if (followups && this._widget) {
const widget = this._widget;
Expand Down
7 changes: 6 additions & 1 deletion src/vscode-dts/vscode.proposed.chatParticipantAdditions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,9 +281,14 @@ declare module 'vscode' {
kind: 'bug';
}

export interface ChatEditorAction {
kind: 'editor';
accepted: boolean;
}

export interface ChatUserActionEvent {
readonly result: ChatResult;
readonly action: ChatCopyAction | ChatInsertAction | ChatTerminalAction | ChatCommandAction | ChatFollowupAction | ChatBugReportAction;
readonly action: ChatCopyAction | ChatInsertAction | ChatTerminalAction | ChatCommandAction | ChatFollowupAction | ChatBugReportAction | ChatEditorAction;
}

export interface ChatVariableValue {
Expand Down

0 comments on commit 6a06de0

Please sign in to comment.