mirror of
https://github.com/microsoft/vscode.git
synced 2025-12-24 20:26:08 +00:00
Hook up feedback/user-action events for inline chat participants (#210040)
- add a special user-action - inline chat controller doesn't call handleFeedback directly anymore - ReplyResonse knows its chat model response
This commit is contained in:
@@ -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 };
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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';
|
||||
@@ -388,7 +388,7 @@ export class ReportIssueForBugCommand extends AbstractInlineChatAction {
|
||||
}
|
||||
|
||||
override runInlineChatCommand(_accessor: ServicesAccessor, ctrl: InlineChatController): void {
|
||||
ctrl.feedbackLast(InlineChatResponseFeedbackKind.Bug);
|
||||
ctrl.reportBug();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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'
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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,
|
||||
) {
|
||||
|
||||
@@ -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 {};
|
||||
@@ -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();
|
||||
@@ -141,9 +143,6 @@ class BridgeAgent implements IChatAgentImplementation {
|
||||
|
||||
this._postLastResponse({ id: request.requestId, response });
|
||||
|
||||
// TODO@jrieken
|
||||
// result?.placeholder
|
||||
// result?.wholeRange
|
||||
|
||||
return {
|
||||
metadata: {
|
||||
@@ -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}"`);
|
||||
|
||||
} 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();
|
||||
|
||||
|
||||
@@ -465,7 +465,8 @@ export class InlineChatSessionServiceImpl implements IInlineChatSessionService {
|
||||
session.textModelN.uri,
|
||||
modelAltVersionIdNow,
|
||||
[],
|
||||
e.request.id
|
||||
e.request.id,
|
||||
e.request.response
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user