Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #214481: Add option to filter code from copilot chat text to speech #230012

Open
wants to merge 11 commits into
base: main
Choose a base branch
from

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,6 @@ export class UtilityProcess extends Disposable {
const args = this.configuration.args ?? [];
const execArgv = this.configuration.execArgv ?? [];
const allowLoadingUnsignedLibraries = this.configuration.allowLoadingUnsignedLibraries;
const respondToAuthRequestsFromMainProcess = this.configuration.respondToAuthRequestsFromMainProcess;
const stdio = 'pipe';
const env = this.createEnv(configuration);

Expand All @@ -247,7 +246,6 @@ export class UtilityProcess extends Disposable {
env,
execArgv,
allowLoadingUnsignedLibraries,
respondToAuthRequestsFromMainProcess,
stdio
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -759,6 +759,12 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen
'minimum': 0,
'tags': ['accessibility']
},
[AccessibilityVoiceSettingId.IgnoreCodeBlocks]: {
'markdownDescription': localize('voice.ignoreCodeBlocks', "Whether to ignore code snippets from the text to speech in copilot chat."),
'type': 'boolean',
'default': true,
'tags': ['accessibility']
},
[AccessibilityVoiceSettingId.SpeechLanguage]: {
'markdownDescription': localize('voice.speechLanguage', "The language that text-to-speech and speech-to-text should use. Select `auto` to use the configured display language if possible. Note that not all display languages maybe supported by speech recognition and synthesizers."),
'type': 'string',
Expand All @@ -774,7 +780,7 @@ export class DynamicSpeechAccessibilityConfiguration extends Disposable implemen
'enumDescriptions': [
localize('accessibility.voice.autoSynthesize.on', "Enable the feature. When a screen reader is enabled, note that this will disable aria updates."),
localize('accessibility.voice.autoSynthesize.off', "Disable the feature."),
localize('accessibility.voice.autoSynthesize.auto', "When a screen reader is detected, disable the feature. Otherwise, enable the feature.")
localize('accessibility.voice.autoSynthesize.auto', "When a screen reader is detected, disable the feature. Otherwise, enable the feature."),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed

],
'markdownDescription': localize('autoSynthesize', "Whether a textual response should automatically be read out aloud when speech was used as input. For example in a chat session, a response is automatically synthesized when voice was used as chat request."),
'default': this.productService.quality !== 'stable' ? 'auto' : 'off', // TODO@bpasero decide on a default
Expand Down
6 changes: 1 addition & 5 deletions src/vs/workbench/contrib/chat/browser/chatListRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -395,11 +395,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
this.renderDetail(element, templateData);
}

if (element.isDisabled) {
templateData.disabledOverlay.classList.add('disabled');
} else {
templateData.disabledOverlay.classList.remove('disabled');
}
templateData.disabledOverlay.classList.toggle('disabled', element.isDisabled);

if (isRequestVM(element) && element.confirmation) {
this.renderConfirmationAction(element, templateData);
Expand Down
12 changes: 12 additions & 0 deletions src/vs/workbench/contrib/chat/common/chatModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,18 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel
this.id = 'response_' + ChatResponseModel.nextId++;
}

withoutCodeBlocks(): string {
const delimiter = '```';
let filtered = this._response.toString();
let start = filtered.indexOf(delimiter);
while (start >= 0) {
const end = filtered.indexOf(delimiter, start + delimiter.length);
filtered = filtered.slice(0, start) + filtered.slice(end + delimiter.length);
start = filtered.indexOf(delimiter);
}
return filtered;
}

/**
* Apply a progress update to the actual response content.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -787,7 +787,8 @@ class ChatSynthesizerSessions {

constructor(
@ISpeechService private readonly speechService: ISpeechService,
@IInstantiationService private readonly instantiationService: IInstantiationService
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IConfigurationService private readonly configurationService: IConfigurationService
) { }

async start(controller: IChatSynthesizerSessionController): Promise<void> {
Expand Down Expand Up @@ -835,11 +836,13 @@ class ChatSynthesizerSessions {
private async *nextChatResponseChunk(response: IChatResponseModel, token: CancellationToken): AsyncIterable<string> {
let totalOffset = 0;
let complete = false;
let isWithinCodeBlock = false;
do {
const responseLength = response.response.toString().length;
const { chunk, offset } = this.parseNextChatResponseChunk(response, totalOffset);
const { chunk, offset, codeBlockTerminated } = this.parseNextChatResponseChunk(response, totalOffset, isWithinCodeBlock);
totalOffset = offset;
complete = response.isComplete;
isWithinCodeBlock = !codeBlockTerminated;

if (chunk) {
yield chunk;
Expand All @@ -855,10 +858,38 @@ class ChatSynthesizerSessions {
} while (!token.isCancellationRequested && !complete);
}

private parseNextChatResponseChunk(response: IChatResponseModel, offset: number): { readonly chunk: string | undefined; readonly offset: number } {
let chunk: string | undefined = undefined;
private withoutCodeBlocks(text: string): [string, boolean] {
const delimiter = '```';
let start = text.indexOf(delimiter);
while (start >= 0) {
const end = text.indexOf(delimiter, start + delimiter.length);
if (end === -1) {
return [text.substring(0, start), false];
}
text = text.slice(0, start) + text.slice(end + delimiter.length);
start = text.indexOf(delimiter);
}
return [text, true];
}

const text = response.response.toString();
private parseNextChatResponseChunk(response: IChatResponseModel, offset: number, isWithinCodeBlock = false): { readonly chunk: string | undefined; readonly offset: number; readonly codeBlockTerminated: boolean } {
let chunk: string | undefined = undefined;
const ignoreCodeBlocks = this.configurationService.getValue<boolean>(AccessibilityVoiceSettingId.IgnoreCodeBlocks);
let text = response.response.toString();
const delimiter = '```';
let codeBlockTerminates = false;
const firstDelimiterIndex = text.indexOf(delimiter);

if (ignoreCodeBlocks) {
if (isWithinCodeBlock && firstDelimiterIndex === -1) {
text = '';
} else if (isWithinCodeBlock && firstDelimiterIndex >= 0) {
text = text.substring(firstDelimiterIndex + 1);
}
const [filteredText, terminated] = this.withoutCodeBlocks(text);
text = filteredText;
codeBlockTerminates = terminated;
}

if (response.isComplete) {
chunk = text.substring(offset);
Expand All @@ -871,7 +902,8 @@ class ChatSynthesizerSessions {

return {
chunk: chunk ? renderStringAsPlaintext({ value: chunk }) : chunk, // convert markdown to plain text
offset
offset,
codeBlockTerminated: codeBlockTerminates
};
}

Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/contrib/speech/common/speechService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ export const enum AccessibilityVoiceSettingId {
SpeechTimeout = 'accessibility.voice.speechTimeout',
AutoSynthesize = 'accessibility.voice.autoSynthesize',
SpeechLanguage = 'accessibility.voice.speechLanguage',
IgnoreCodeBlocks = 'accessibility.voice.ignoreCodeBlocks'
}

export const SPEECH_LANGUAGE_CONFIG = AccessibilityVoiceSettingId.SpeechLanguage;
Expand Down