Path: blob/main/src/vs/workbench/contrib/chat/browser/chatResponseAccessibleView.ts
3296 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { renderAsPlaintext } from '../../../../base/browser/markdownRenderer.js';6import { isMarkdownString, MarkdownString } from '../../../../base/common/htmlContent.js';7import { stripIcons } from '../../../../base/common/iconLabels.js';8import { Disposable } from '../../../../base/common/lifecycle.js';9import { AccessibleViewProviderId, AccessibleViewType, IAccessibleViewContentProvider } from '../../../../platform/accessibility/browser/accessibleView.js';10import { IAccessibleViewImplementation } from '../../../../platform/accessibility/browser/accessibleViewRegistry.js';11import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';12import { AccessibilityVerbositySettingId } from '../../accessibility/browser/accessibilityConfiguration.js';13import { migrateLegacyTerminalToolSpecificData } from '../common/chat.js';14import { ChatContextKeys } from '../common/chatContextKeys.js';15import { isResponseVM } from '../common/chatViewModel.js';16import { ChatTreeItem, IChatWidget, IChatWidgetService } from './chat.js';1718export class ChatResponseAccessibleView implements IAccessibleViewImplementation {19readonly priority = 100;20readonly name = 'panelChat';21readonly type = AccessibleViewType.View;22readonly when = ChatContextKeys.inChatSession;23getProvider(accessor: ServicesAccessor) {24const widgetService = accessor.get(IChatWidgetService);25const widget = widgetService.lastFocusedWidget;26if (!widget) {27return;28}29const chatInputFocused = widget.hasInputFocus();30if (chatInputFocused) {31widget.focusLastMessage();32}3334const verifiedWidget: IChatWidget = widget;35const focusedItem = verifiedWidget.getFocus();36if (!focusedItem) {37return;38}3940return new ChatResponseAccessibleProvider(verifiedWidget, focusedItem, chatInputFocused);41}42}4344class ChatResponseAccessibleProvider extends Disposable implements IAccessibleViewContentProvider {45private _focusedItem: ChatTreeItem;46constructor(47private readonly _widget: IChatWidget,48item: ChatTreeItem,49private readonly _chatInputFocused: boolean50) {51super();52this._focusedItem = item;53}5455readonly id = AccessibleViewProviderId.PanelChat;56readonly verbositySettingKey = AccessibilityVerbositySettingId.Chat;57readonly options = { type: AccessibleViewType.View };5859provideContent(): string {60return this._getContent(this._focusedItem);61}6263private _getContent(item: ChatTreeItem): string {64let responseContent = isResponseVM(item) ? item.response.toString() : '';65if (!responseContent && 'errorDetails' in item && item.errorDetails) {66responseContent = item.errorDetails.message;67}68if (isResponseVM(item)) {69item.response.value.filter(item => item.kind === 'elicitation').forEach(elicitation => {70const title = elicitation.title;71if (typeof title === 'string') {72responseContent += `${title}\n`;73} else if (isMarkdownString(title)) {74responseContent += renderAsPlaintext(title, { includeCodeBlocksFences: true }) + '\n';75}76const message = elicitation.message;77if (isMarkdownString(message)) {78responseContent += renderAsPlaintext(message, { includeCodeBlocksFences: true });79} else {80responseContent += message;81}82});83const toolInvocations = item.response.value.filter(item => item.kind === 'toolInvocation');84for (const toolInvocation of toolInvocations) {85if (toolInvocation.confirmationMessages) {86const title = typeof toolInvocation.confirmationMessages.title === 'string' ? toolInvocation.confirmationMessages.title : toolInvocation.confirmationMessages.title.value;87const message = typeof toolInvocation.confirmationMessages.message === 'string' ? toolInvocation.confirmationMessages.message : stripIcons(renderAsPlaintext(toolInvocation.confirmationMessages.message));88let input = '';89if (toolInvocation.toolSpecificData) {90if (toolInvocation.toolSpecificData?.kind === 'terminal') {91const terminalData = migrateLegacyTerminalToolSpecificData(toolInvocation.toolSpecificData);92input = terminalData.commandLine.userEdited ?? terminalData.commandLine.toolEdited ?? terminalData.commandLine.original;93} else {94input = toolInvocation.toolSpecificData?.kind === 'extensions'95? JSON.stringify(toolInvocation.toolSpecificData.extensions)96: toolInvocation.toolSpecificData?.kind === 'todoList'97? JSON.stringify(toolInvocation.toolSpecificData.todoList)98: toolInvocation.toolSpecificData?.kind === 'pullRequest'99? JSON.stringify(toolInvocation.toolSpecificData)100: JSON.stringify(toolInvocation.toolSpecificData.rawInput);101}102}103responseContent += `${title}`;104if (input) {105responseContent += `: ${input}`;106}107responseContent += `\n${message}\n`;108} else if (toolInvocation.isComplete && toolInvocation.resultDetails && 'input' in toolInvocation.resultDetails) {109responseContent += '\n' + toolInvocation.resultDetails.isError ? 'Errored ' : 'Completed ';110responseContent += `${`${typeof toolInvocation.invocationMessage === 'string' ? toolInvocation.invocationMessage : stripIcons(renderAsPlaintext(toolInvocation.invocationMessage))} with input: ${toolInvocation.resultDetails.input}`}\n`;111}112}113114const pastConfirmations = item.response.value.filter(item => item.kind === 'toolInvocationSerialized');115for (const pastConfirmation of pastConfirmations) {116if (pastConfirmation.isComplete && pastConfirmation.resultDetails && 'input' in pastConfirmation.resultDetails) {117if (pastConfirmation.pastTenseMessage) {118responseContent += `\n${`${typeof pastConfirmation.pastTenseMessage === 'string' ? pastConfirmation.pastTenseMessage : stripIcons(renderAsPlaintext(pastConfirmation.pastTenseMessage))} with input: ${pastConfirmation.resultDetails.input}`}\n`;119}120}121}122}123return renderAsPlaintext(new MarkdownString(responseContent), { includeCodeBlocksFences: true });124}125126onClose(): void {127this._widget.reveal(this._focusedItem);128if (this._chatInputFocused) {129this._widget.focusInput();130} else {131this._widget.focus(this._focusedItem);132}133}134135provideNextContent(): string | undefined {136const next = this._widget.getSibling(this._focusedItem, 'next');137if (next) {138this._focusedItem = next;139return this._getContent(next);140}141return;142}143144providePreviousContent(): string | undefined {145const previous = this._widget.getSibling(this._focusedItem, 'previous');146if (previous) {147this._focusedItem = previous;148return this._getContent(previous);149}150return;151}152}153154155