Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/vs/workbench/api/common/extHostTypeConverters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2339,8 +2339,12 @@ export namespace ChatResponseProgress {
} else if ('agentName' in progress) {
checkProposedApiEnabled(extension, 'chatAgents2Additions');
return { agentName: progress.agentName, command: progress.command, kind: 'agentDetection' };
} else {
} else if ('treeData' in progress) {
return { treeData: progress.treeData, kind: 'treeData' };
} else if ('message' in progress) {
return { content: progress.message, kind: 'progressMessage' };
} else {
throw new Error('Invalid progress type: ' + JSON.stringify(progress));
}
}
}
Expand Down
25 changes: 21 additions & 4 deletions src/vs/workbench/contrib/chat/browser/chatListRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ interface IChatListItemTemplate {
readonly agentAvatarContainer: HTMLElement;
readonly username: HTMLElement;
readonly detail: HTMLElement;
readonly progressSteps: HTMLElement;
readonly value: HTMLElement;
readonly referencesListContainer: HTMLElement;
readonly contextKeyService: IContextKeyService;
Expand Down Expand Up @@ -230,6 +231,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
const detailContainer = dom.append(user, $('span.detail-container'));
const detail = dom.append(detailContainer, $('span.detail'));
dom.append(detailContainer, $('span.chat-animated-ellipsis'));
const progressSteps = dom.append(rowContainer, $('.progress-steps'));
const referencesListContainer = dom.append(rowContainer, $('.referencesListContainer'));
const value = dom.append(rowContainer, $('.value'));
const elementDisposables = new DisposableStore();
Expand All @@ -250,7 +252,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
}));


const template: IChatListItemTemplate = { avatarContainer, agentAvatarContainer, username, detail, referencesListContainer, value, rowContainer, elementDisposables, titleToolbar, templateDisposables, contextKeyService };
const template: IChatListItemTemplate = { avatarContainer, agentAvatarContainer, username, detail, progressSteps, referencesListContainer, value, rowContainer, elementDisposables, titleToolbar, templateDisposables, contextKeyService };
return template;
}

Expand Down Expand Up @@ -285,7 +287,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch

dom.clearNode(templateData.detail);
if (isResponseVM(element)) {
this.renderProgressMessage(element, templateData);
this.renderDetail(element, templateData);
this.renderProgressSteps(element, templateData);
}

// Do a progressive render if
Expand Down Expand Up @@ -325,7 +328,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
}
}

private renderProgressMessage(element: IChatResponseViewModel, templateData: IChatListItemTemplate): void {
private renderDetail(element: IChatResponseViewModel, templateData: IChatListItemTemplate): void {
let progressMsg: string = '';
if (element.agent && !element.agent.metadata.isDefault) {
let usingMsg = chatAgentLeader + element.agent.id;
Expand All @@ -350,6 +353,20 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
}
}

private renderProgressSteps(element: IChatResponseViewModel, templateData: IChatListItemTemplate): void {
dom.clearNode(templateData.progressSteps);
if (element.response.value.length) {
return;
}

element.progressMessages.forEach((msg, index) => {
const last = index === element.progressMessages.length - 1;
const icon = last ? ThemeIcon.modify(Codicon.sync, 'spin') : Codicon.check;
const step = dom.$('.progress-step', undefined, renderIcon(icon), dom.$('span.progress-step-message', undefined, msg.content));
templateData.progressSteps.appendChild(step);
});
}

private renderAvatar(element: ChatTreeItem, templateData: IChatListItemTemplate): void {
if (element.avatarIconUri) {
const avatarImgIcon = dom.$<HTMLImageElement>('img.icon');
Expand Down Expand Up @@ -407,7 +424,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
dom.clearNode(templateData.referencesListContainer);

if (isResponseVM(element)) {
this.renderProgressMessage(element, templateData);
this.renderDetail(element, templateData);
}

this.renderContentReferencesIfNeeded(element, templateData, templateData.elementDisposables);
Expand Down
18 changes: 18 additions & 0 deletions src/vs/workbench/contrib/chat/browser/media/chat.css
Original file line number Diff line number Diff line change
Expand Up @@ -586,3 +586,21 @@
.interactive-session .chat-used-context .chat-used-context-label .monaco-button .codicon {
margin: 0 0 0 4px;
}

.interactive-item-container .progress-steps {
display: flex;
flex-direction: column;
gap: 4px;
margin-left: 4px;
}

.interactive-item-container .progress-steps .progress-step {
display: flex;
gap: 5px;
align-items: center;
opacity: 0.7;
}

.interactive-item-container .progress-steps .progress-step .codicon-check {
color: var(--vscode-debugIcon-startForeground);
}
34 changes: 24 additions & 10 deletions src/vs/workbench/contrib/chat/common/chatModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { OffsetRange } from 'vs/editor/common/core/offsetRange';
import { ILogService } from 'vs/platform/log/common/log';
import { IChatAgentCommand, IChatAgentData, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents';
import { ChatRequestTextPart, IParsedChatRequest, reviveParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { IChat, IChatAsyncContent, IChatContent, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgress, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService';
import { IChat, IChatAsyncContent, IChatContent, IChatContentInlineReference, IChatContentReference, IChatFollowup, IChatMarkdownContent, IChatProgress, IChatProgressMessage, IChatReplyFollowup, IChatResponse, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatTreeData, IChatUsedContext, InteractiveSessionVoteDirection, isIUsedContext } from 'vs/workbench/contrib/chat/common/chatService';

export interface IChatRequestModel {
readonly id: string;
Expand Down Expand Up @@ -49,6 +49,7 @@ export interface IChatResponseModel {
readonly agent?: IChatAgentData;
readonly usedContext: IChatUsedContext | undefined;
readonly contentReferences: ReadonlyArray<IChatContentReference>;
readonly progressMessages: ReadonlyArray<IChatProgressMessage>;
readonly slashCommand?: IChatAgentCommand;
readonly response: IResponse;
readonly isComplete: boolean;
Expand Down Expand Up @@ -249,6 +250,10 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel
return this._contentReferences;
}

private readonly _progressMessages: IChatProgressMessage[] = [];
public get progressMessages(): ReadonlyArray<IChatProgressMessage> {
return this._progressMessages;
}

constructor(
_response: IMarkdownString | ReadonlyArray<IMarkdownString | IChatResponseProgressFileTreeData | IChatContentInlineReference>,
Expand All @@ -269,15 +274,24 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel
this._id = 'response_' + ChatResponseModel.nextId++;
}

/**
* Apply a progress update to the actual response content.
*/
updateContent(responsePart: IChatProgressResponseContent | IChatContent, quiet?: boolean) {
this._response.updateContent(responsePart, quiet);
}

updateReferences(reference: IChatUsedContext | IChatContentReference) {
if (reference.kind === 'usedContext') {
this._usedContext = reference;
} else if (reference.kind === 'reference') {
this._contentReferences.push(reference);
/**
* Apply one of the progress updates that are not part of the actual response content.
*/
applyProgress(progress: IChatUsedContext | IChatContentReference | IChatProgressMessage) {
if (progress.kind === 'usedContext') {
this._usedContext = progress;
} else if (progress.kind === 'reference') {
this._contentReferences.push(progress);
this._onDidChange.fire();
} else if (progress.kind === 'progressMessage') {
this._progressMessages.push(progress);
this._onDidChange.fire();
}
}
Expand Down Expand Up @@ -528,11 +542,11 @@ export class ChatModel extends Disposable implements IChatModel {
revive<ISerializableChatAgentData>(raw.agent) : undefined;
request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, request.id, true, raw.isCanceled, raw.vote, raw.responseErrorDetails, raw.followups);
if (raw.usedContext) { // @ulugbekna: if this's a new vscode sessions, doc versions are incorrect anyway?
request.response.updateReferences(raw.usedContext);
request.response.applyProgress(raw.usedContext);
}

if (raw.contentReferences) {
raw.contentReferences.forEach(r => request.response!.updateReferences(r));
raw.contentReferences.forEach(r => request.response!.applyProgress(r));
}
}
return request;
Expand Down Expand Up @@ -628,8 +642,8 @@ export class ChatModel extends Disposable implements IChatModel {

if (progress.kind === 'content' || progress.kind === 'markdownContent' || progress.kind === 'asyncContent' || progress.kind === 'treeData' || progress.kind === 'inlineReference') {
request.response.updateContent(progress, quiet);
} else if (progress.kind === 'usedContext' || progress.kind === 'reference') {
request.response.updateReferences(progress);
} else if (progress.kind === 'usedContext' || progress.kind === 'reference' || progress.kind === 'progressMessage') {
request.response.applyProgress(progress);
} else if (progress.kind === 'agentDetection') {
const agent = this.chatAgentService.getAgent(progress.agentName);
if (agent) {
Expand Down
8 changes: 7 additions & 1 deletion src/vs/workbench/contrib/chat/common/chatService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,11 @@ export interface IChatAsyncContent {
kind: 'asyncContent';
}

export interface IChatProgressMessage {
content: string;
kind: 'progressMessage';
}

export type IChatProgress =
| IChatContent
| IChatMarkdownContent
Expand All @@ -133,7 +138,8 @@ export type IChatProgress =
| IChatUsedContext
| IChatContentReference
| IChatContentInlineReference
| IChatAgentDetection;
| IChatAgentDetection
| IChatProgressMessage;

export interface IChatProvider {
readonly id: string;
Expand Down
7 changes: 6 additions & 1 deletion src/vs/workbench/contrib/chat/common/chatViewModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { ILogService } from 'vs/platform/log/common/log';
import { IChatAgentCommand, IChatAgentData } from 'vs/workbench/contrib/chat/common/chatAgents';
import { ChatModelInitState, IChatModel, IChatRequestModel, IChatResponseModel, IChatWelcomeMessageContent, IResponse } from 'vs/workbench/contrib/chat/common/chatModel';
import { IParsedChatRequest } from 'vs/workbench/contrib/chat/common/chatParserTypes';
import { IChatContentReference, IChatReplyFollowup, IChatResponseCommandFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatUsedContext, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { IChatContentReference, IChatProgressMessage, IChatReplyFollowup, IChatResponseCommandFollowup, IChatResponseErrorDetails, IChatResponseProgressFileTreeData, IChatUsedContext, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService';
import { countWords } from 'vs/workbench/contrib/chat/common/chatWordCounter';

export function isRequestVM(item: unknown): item is IChatRequestViewModel {
Expand Down Expand Up @@ -96,6 +96,7 @@ export interface IChatResponseViewModel {
readonly response: IResponse;
readonly usedContext: IChatUsedContext | undefined;
readonly contentReferences: ReadonlyArray<IChatContentReference>;
readonly progressMessages: ReadonlyArray<IChatProgressMessage>;
readonly isComplete: boolean;
readonly isCanceled: boolean;
readonly vote: InteractiveSessionVoteDirection | undefined;
Expand Down Expand Up @@ -297,6 +298,10 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi
return this._model.contentReferences;
}

get progressMessages(): ReadonlyArray<IChatProgressMessage> {
return this._model.progressMessages;
}

get isComplete() {
return this._model.isComplete;
}
Expand Down
11 changes: 9 additions & 2 deletions src/vscode-dts/vscode.proposed.chatAgents2.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,14 +249,21 @@ declare module 'vscode' {
variables: Record<string, ChatVariableValue[]>;
}

// TODO@API should these each be prefixed ChatAgentProgress*?
export type ChatAgentProgress =
| ChatAgentContent
| ChatAgentTask
| ChatAgentFileTree
| ChatAgentUsedContext
| ChatAgentContentReference
| ChatAgentInlineContentReference;
| ChatAgentInlineContentReference
| ChatAgentProgressMessage;

/**
* Is displayed in the UI to communicate steps of progress to the user. Should be used when the agent may be slow to respond, e.g. due to doing extra work before sending the actual request to the LLM.
*/
export interface ChatAgentProgressMessage {
message: string;
}

/**
* Indicates a piece of content that was used by the chat agent while processing the request. Will be displayed to the user.
Expand Down