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
4 changes: 2 additions & 2 deletions src/vs/workbench/contrib/chat/browser/chat.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1247,11 +1247,11 @@ configurationRegistry.registerConfiguration({
type: 'array',
items: { type: 'string' },
default: [],
description: nls.localize('chat.agent.thinking.phrases.phrases', "Custom loading messages to show during thinking, terminal, and tool operations.")
description: nls.localize('chat.agent.thinking.phrases.phrases', "Custom loading messages to show during thinking, working progress, terminal, and tool operations.")
}
},
additionalProperties: false,
markdownDescription: nls.localize('chat.agent.thinking.phrases', "Customize the loading messages shown during agent operations. Use `\"mode\": \"replace\"` to use only your phrases, or `\"mode\": \"append\"` to add them to the defaults."),
markdownDescription: nls.localize('chat.agent.thinking.phrases', "Customize the loading messages shown during agent thinking and progress indicators. Use `\"mode\": \"replace\"` to use only your phrases, or `\"mode\": \"append\"` to add them to the defaults."),
tags: ['experimental'],
},
[ChatConfiguration.AutoExpandToolFailures]: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { IHoverService } from '../../../../../../platform/hover/browser/hover.js
import { HoverStyle } from '../../../../../../base/browser/ui/hover/hover.js';
import { ILanguageModelToolsService } from '../../../common/tools/languageModelToolsService.js';
import { isEqual } from '../../../../../../base/common/resources.js';
import { buildPhrasePool } from './chatThinkingContentPart.js';

export class ChatProgressContentPart extends Disposable implements IChatContentPart {
public readonly domNode: HTMLElement;
Expand Down Expand Up @@ -174,9 +175,13 @@ export class ChatWorkingProgressContentPart extends ChatProgressContentPart impl
@IConfigurationService configurationService: IConfigurationService,
@ILanguageModelToolsService languageModelToolsService: ILanguageModelToolsService
) {
const defaultLabel = localize('workingMessage', "Working");
const pool = buildPhrasePool([defaultLabel], configurationService);
const label = pool[Math.floor(Math.random() * pool.length)];

const progressMessage: IChatProgressMessage = {
kind: 'progressMessage',
content: new MarkdownString().appendText(localize('workingMessage', "Working"))
content: new MarkdownString().appendText(label)
};
super(progressMessage, chatContentMarkdownRenderer, context, undefined, undefined, undefined, undefined, true, instantiationService, chatMarkdownAnchorService, configurationService);
this._register(languageModelToolsService.onDidPrepareToolCallBecomeUnresponsive(e => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,26 @@ const toolMessages = [
localize('chat.thinking.tool.5', 'Evaluating'),
];

/**
* Builds a phrase pool from defaults and user-configured custom phrases.
* In 'replace' mode, only custom phrases are used; in 'append' mode (default),
* custom phrases are added to the defaults.
*/
export function buildPhrasePool(defaults: string[], configurationService: IConfigurationService): string[] {
const config = configurationService.getValue<{ mode?: 'replace' | 'append'; phrases?: string[] }>(ChatConfiguration.ThinkingPhrases);
const customPhrases = Array.isArray(config?.phrases)
? config.phrases
.filter((phrase): phrase is string => typeof phrase === 'string')
.map(phrase => phrase.trim())
.filter(phrase => phrase.length > 0)
: [];

if (customPhrases.length > 0) {
return config?.mode === 'replace' ? [...customPhrases] : [...defaults, ...customPhrases];
}
return [...defaults];
}

export class ChatThinkingContentPart extends ChatCollapsibleContentPart implements IChatContentPart {

private static _codeBlockRendererSync(_languageId: string, text: string, _raw?: string): HTMLElement {
Expand All @@ -157,7 +177,8 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen
private content: IChatThinkingPart;
private currentThinkingValue: string;
private currentTitle: string;
private defaultTitle = localize('chat.thinking.header', 'Working');
private defaultTitle = localize('chat.thinking.header', 'Thinking');
private readonly workingTitle = localize('chat.thinking.header.working', 'Working');
private textContainer!: HTMLElement;
private readonly _markdownResult = this._register(new MutableDisposable<IRenderedMarkdown>());
private wrapper!: HTMLElement;
Expand Down Expand Up @@ -195,38 +216,18 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen
let defaults: string[];
switch (category) {
case WorkingMessageCategory.Thinking:
defaults = [...defaultThinkingMessages];
defaults = defaultThinkingMessages;
break;
case WorkingMessageCategory.Terminal:
defaults = [...terminalMessages];
defaults = terminalMessages;
break;
case WorkingMessageCategory.Tool:
default:
defaults = [...toolMessages];
defaults = toolMessages;
break;
}

// Read configured phrases from the single setting
const config = this.configurationService.getValue<{ mode?: 'replace' | 'append'; phrases?: string[] }>(ChatConfiguration.ThinkingPhrases);
const customPhrases = Array.isArray(config?.phrases)
? config.phrases
.filter((phrase): phrase is string => typeof phrase === 'string')
.map(phrase => phrase.trim())
.filter(phrase => phrase.length > 0)
: [];
const mode = config?.mode === 'replace' ? 'replace' : 'append';

if (customPhrases.length > 0) {
if (mode === 'replace') {
// Replace mode: use only custom phrases for all categories
pool = [...customPhrases];
} else {
// Append mode: add custom phrases to defaults for this category
pool = [...defaults, ...customPhrases];
}
} else {
pool = defaults;
}
pool = buildPhrasePool(defaults, this.configurationService);

this.availableMessagesByCategory.set(category, pool);
}
Expand All @@ -247,7 +248,7 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen
) {
const initialText = extractTextFromPart(content);
const extractedTitle = extractTitleFromThinkingContent(initialText)
?? 'Working';
?? localize('chat.thinking.header.initial', 'Thinking');

super(extractedTitle, context, undefined, hoverService, configurationService);

Expand Down Expand Up @@ -1309,6 +1310,11 @@ ${this.hookCount > 0 ? `EXAMPLES WITH BLOCKED CONTENT (from hooks):
this.toolInvocationCount++;
}

// Shift default title from 'Thinking' to 'Working' once we have tool calls
if (this.toolInvocationCount === 1) {
this.defaultTitle = this.workingTitle;
}
Comment thread
justschen marked this conversation as resolved.

let toolCallLabel: string;

const isToolInvocation = toolInvocationOrMarkdown && (toolInvocationOrMarkdown.kind === 'toolInvocation' || toolInvocationOrMarkdown.kind === 'toolInvocationSerialized');
Expand Down Expand Up @@ -1594,7 +1600,7 @@ ${this.hookCount > 0 ? `EXAMPLES WITH BLOCKED CONTENT (from hooks):
}

this.lastExtractedTitle = title;
const thinkingLabel = `Working: ${title}`;
const thinkingLabel = localize('chat.thinking.label', "{0}: {1}", this.defaultTitle, title);
this.currentTitle = thinkingLabel;

if (!this._collapseButton) {
Expand All @@ -1609,7 +1615,7 @@ ${this.hookCount > 0 ? `EXAMPLES WITH BLOCKED CONTENT (from hooks):
this.titleShimmerSpan = $('span.chat-thinking-title-shimmer');
labelElement.appendChild(this.titleShimmerSpan);
}
this.titleShimmerSpan.textContent = 'Working: ';
this.titleShimmerSpan.textContent = localize('chat.thinking.shimmer', "{0}: ", this.defaultTitle);

// Dispose previous detail rendering
this._titleDetailRendered.clear();
Expand Down
Loading