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