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
5 changes: 5 additions & 0 deletions build/lib/stylelint/vscode-known-variables.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@
"--vscode-chat-avatarForeground",
"--vscode-chat-checkpointSeparator",
"--vscode-chat-editedFileForeground",
"--vscode-chat-inputWorkingBorderColor1",
"--vscode-chat-inputWorkingBorderColor2",
"--vscode-chat-inputWorkingBorderColor3",
"--vscode-chat-linesAddedForeground",
"--vscode-chat-linesRemovedForeground",
"--vscode-chat-requestBackground",
Expand Down Expand Up @@ -1037,6 +1040,8 @@
"--monaco-editor-warning-decoration",
"--animation-angle",
"--animation-opacity",
"--chat-input-anim-angle",
"--chat-input-working-fill",
"--chat-setup-dialog-glow-angle",
"--vscode-chat-font-family",
"--vscode-chat-font-size-body-l",
Expand Down
9 changes: 9 additions & 0 deletions src/vs/sessions/browser/media/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -387,12 +387,21 @@
border-color: var(--vscode-agentsChatInput-border) !important;
background-color: var(--vscode-agentsChatInput-background);
color: var(--vscode-agentsChatInput-foreground);
/* Preserve the agents-app input background under the developer-joy ring. */
--chat-input-working-fill: var(--vscode-agentsChatInput-background);
}

.agent-sessions-workbench .interactive-session .chat-input-container.focused {
border-color: var(--vscode-agentsChatInput-focusBorder, var(--vscode-focusBorder)) !important;
}

/* While the developer-joy animated border is active, suppress the static
border so it doesn't visually conflict with the spinning gradient ring. */
.agent-sessions-workbench .interactive-session .chat-input-container.working,
.agent-sessions-workbench .interactive-session .chat-input-container.working.focused {
border-color: transparent !important;
}

/* Make the Monaco editor inside the chat input transparent so it inherits the chatInput.background */
.agent-sessions-workbench .interactive-session .interactive-input-part .chat-editor-container .interactive-input-editor .monaco-editor,
.agent-sessions-workbench .interactive-session .interactive-input-part .chat-editor-container .interactive-input-editor .monaco-editor .monaco-editor-background {
Expand Down
7 changes: 6 additions & 1 deletion src/vs/workbench/contrib/chat/browser/chat.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -638,7 +638,12 @@ configurationRegistry.registerConfiguration({
[ChatConfiguration.ChatPersistentProgressEnabled]: {
type: 'boolean',
default: product.quality !== 'stable',
description: nls.localize('chat.persistentProgress.enabled', "Show elapsed time and token usage in chat response progress."),
description: nls.localize('chat.persistentProgress.enabled', "Always show progress in chat."),
Comment thread
justschen marked this conversation as resolved.
},
[ChatConfiguration.ProgressBorder]: {
type: 'boolean',
default: false,
markdownDescription: nls.localize('chat.progressBorder.enabled', "Show an animated gradient border around the chat input while the agent is working or thinking. When enabled, this overrides {0} to be off.", '`#chat.persistentProgress.enabled#`'),
},
[ChatConfiguration.NotifyWindowOnResponseReceived]: {
type: 'string',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,8 @@ export class ChatWorkingProgressContentPart extends Disposable implements IChatC
) {
super();
this.explicitContent = workingProgress.content;
const persistentProgressEnabled = configurationService.getValue<boolean>(ChatConfiguration.ChatPersistentProgressEnabled) !== false;
const persistentProgressEnabled = configurationService.getValue<boolean>(ChatConfiguration.ChatPersistentProgressEnabled) !== false
&& configurationService.getValue<boolean>(ChatConfiguration.ProgressBorder) !== true;
if (persistentProgressEnabled) {
const pool = buildPhrasePool(defaultThinkingMessages, configurationService);
this.label = pool[Math.floor(Math.random() * pool.length)];
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,8 @@ export class ChatThinkingContentPart extends ChatCollapsibleContentPart implemen
this.id = content.id;
this.content = content;
this.allThinkingParts.push(content);
this.showProgressDetails = this.configurationService.getValue<boolean>(ChatConfiguration.ChatPersistentProgressEnabled) !== false;
this.showProgressDetails = this.configurationService.getValue<boolean>(ChatConfiguration.ChatPersistentProgressEnabled) !== false
&& this.configurationService.getValue<boolean>(ChatConfiguration.ProgressBorder) !== true;
const configuredMode = this.configurationService.getValue<ThinkingDisplayMode>('chat.agent.thinkingStyle') ?? ThinkingDisplayMode.Collapsed;

this.fixedScrollingMode = configuredMode === ThinkingDisplayMode.FixedScrolling;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1061,7 +1061,8 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
return undefined;
}

const showProgressDetails = this.configService.getValue<boolean>(ChatConfiguration.ChatPersistentProgressEnabled) !== false;
const showProgressDetails = this.configService.getValue<boolean>(ChatConfiguration.ChatPersistentProgressEnabled) !== false
&& this.configService.getValue<boolean>(ChatConfiguration.ProgressBorder) !== true;
if (element.isComplete) {
return undefined;
}
Expand Down Expand Up @@ -1227,7 +1228,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer<Ch
return;
}

if (element.isComplete && this.configService.getValue<boolean>(ChatConfiguration.ChatPersistentProgressEnabled) !== false) {
if (element.isComplete && this.configService.getValue<boolean>(ChatConfiguration.ChatPersistentProgressEnabled) !== false && this.configService.getValue<boolean>(ChatConfiguration.ProgressBorder) !== true) {
return;
}

Expand Down
20 changes: 20 additions & 0 deletions src/vs/workbench/contrib/chat/browser/widget/chatWidget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,9 @@ export class ChatWidget extends Disposable implements IChatWidget {
this.updateChatViewVisibility();
}
}
if (e.affectsConfiguration(ChatConfiguration.ProgressBorder)) {
this.updateWorkingProgressBorder();
}
}));

this._register(bindContextKey(decidedChatEditingResourceContextKey, contextKeyService, (reader) => {
Expand Down Expand Up @@ -656,6 +659,20 @@ export class ChatWidget extends Disposable implements IChatWidget {
return this.inlineInputPartDisposable.value!;
}

private updateWorkingProgressBorder(): void {
const inputPart = this.inputPartDisposable.value;
if (!inputPart) {
return;
}
const inputContainer = inputPart.inputContainerElement;
if (!inputContainer) {
return;
}
const enabled = this.configurationService.getValue<boolean>(ChatConfiguration.ProgressBorder) === true;
const inProgress = !!this.viewModel?.model.requestInProgress.get();
inputContainer.classList.toggle('working', enabled && inProgress);
Comment thread
justschen marked this conversation as resolved.
}

get inputEditor(): ICodeEditor {
return this.input.inputEditor;
}
Expand Down Expand Up @@ -1980,6 +1997,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
this.finishedEditing();
}
this.viewModel = undefined;
this.updateWorkingProgressBorder();
this.onDidChangeItems();
this._hasPendingRequestsContextKey.set(false);
return;
Expand Down Expand Up @@ -2035,6 +2053,7 @@ export class ChatWidget extends Disposable implements IChatWidget {

this.requestInProgress.set(this.viewModel.model.requestInProgress.get());
this.hasActiveRequest.set(this.viewModel.model.hasActiveRequest.get());
this.updateWorkingProgressBorder();

// Update the editor's placeholder text when it changes in the view model
if (events?.some(e => e?.kind === 'changePlaceholder')) {
Expand All @@ -2053,6 +2072,7 @@ export class ChatWidget extends Disposable implements IChatWidget {
}
// Disposes the viewmodel and listeners
this.viewModel = undefined;
this.updateWorkingProgressBorder();
this.onDidChangeItems();
}));
this._sessionIsEmptyContextKey.set(model.getRequests().length === 0);
Expand Down
66 changes: 66 additions & 0 deletions src/vs/workbench/contrib/chat/browser/widget/media/chat.css
Original file line number Diff line number Diff line change
Expand Up @@ -871,6 +871,72 @@ have to be updated for changes to the rules above, or to support more deeply nes
overflow: hidden;
}

/* Animated gradient border shown around the chat input while the agent is
working or thinking. Toggled by the `chat.progressBorder.enabled`
setting and the chat widget's request-in-progress state. The ring is
rendered using layered `background-image` + `background-clip`
(`padding-box`/`border-box`), so it traces the input's outer corner
radius and isn't clipped by `overflow: hidden` on the parent. */
@property --chat-input-anim-angle {
syntax: '<angle>';
inherits: false;
initial-value: 135deg;
}

@keyframes chat-input-working-border-spin {
from {
--chat-input-anim-angle: 135deg;
}

to {
--chat-input-anim-angle: 495deg;
}
}

@keyframes chat-input-working-border-glow {

0%,
100% {
box-shadow:
0 0 4px 0 color-mix(in srgb, var(--vscode-chat-inputWorkingBorderColor1) 18%, transparent),
0 0 10px 0 color-mix(in srgb, var(--vscode-chat-inputWorkingBorderColor3) 8%, transparent);
}

50% {
box-shadow:
0 0 6px 0 color-mix(in srgb, var(--vscode-chat-inputWorkingBorderColor2) 22%, transparent),
0 0 14px 1px color-mix(in srgb, var(--vscode-chat-inputWorkingBorderColor1) 12%, transparent);
}
}

.monaco-workbench .interactive-session .chat-input-container.working {
border-color: transparent;
/* The padding-box layer fills the input interior. It defaults to the
standard input background, but each host can override
`--chat-input-working-fill` to keep its own background color (e.g. the
Sessions workbench sets it to `--vscode-agentsChatInput-background`).
The conic-gradient layer is clipped to the border box so it paints
exactly where the (transparent) border lives. */
background:
linear-gradient(var(--chat-input-working-fill, var(--vscode-input-background)),
var(--chat-input-working-fill, var(--vscode-input-background))) padding-box,
conic-gradient(from var(--chat-input-anim-angle),
var(--vscode-chat-inputWorkingBorderColor1),
var(--vscode-chat-inputWorkingBorderColor2),
var(--vscode-chat-inputWorkingBorderColor3),
var(--vscode-chat-inputWorkingBorderColor2),
var(--vscode-chat-inputWorkingBorderColor1)) border-box;
Comment thread
justschen marked this conversation as resolved.
animation:
chat-input-working-border-spin 1.2s linear infinite,
chat-input-working-border-glow 3s ease-in-out infinite;
}

@media (prefers-reduced-motion: reduce) {
.monaco-workbench .interactive-session .chat-input-container.working {
animation: none;
}
}

/* Context usage widget container - positioned in the secondary toolbar below input */
.interactive-session .chat-input-toolbars .chat-context-usage-container,
.interactive-session .chat-secondary-toolbar .chat-context-usage-container {
Expand Down
1 change: 1 addition & 0 deletions src/vs/workbench/contrib/chat/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export enum ChatConfiguration {
ChatViewProgressBadgeEnabled = 'chat.viewProgressBadge.enabled',
ChatContextUsageEnabled = 'chat.contextUsage.enabled',
ChatPersistentProgressEnabled = 'chat.persistentProgress.enabled',
ProgressBorder = 'chat.progressBorder.enabled',
SubagentToolCustomAgents = 'chat.customAgentInSubagent.enabled',
GeneralPurposeAgentEnabled = 'chat.generalPurposeAgent.enabled',
SubagentsAllowInvocationsFromSubagents = 'chat.subagents.allowInvocationsFromSubagents',
Expand Down
15 changes: 15 additions & 0 deletions src/vs/workbench/contrib/chat/common/widget/chatColors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,18 @@ export const chatThinkingShimmer = registerColor(
'chat.thinkingShimmer',
{ dark: '#ffffff', light: '#000000', hcDark: '#ffffff', hcLight: '#000000' },
localize('chat.thinkingShimmer', 'Shimmer highlight for thinking/working labels.'), true);

export const chatInputWorkingBorderColor1 = registerColor(
'chat.inputWorkingBorderColor1',
{ dark: '#b44aff', light: '#b44aff', hcDark: '#b44aff', hcLight: '#b44aff' },
localize('chat.inputWorkingBorderColor1', 'First color stop of the animated chat input border shown while a request is in flight.'), true);

export const chatInputWorkingBorderColor2 = registerColor(
'chat.inputWorkingBorderColor2',
{ dark: '#4af0c0', light: '#4af0c0', hcDark: '#4af0c0', hcLight: '#4af0c0' },
localize('chat.inputWorkingBorderColor2', 'Second color stop of the animated chat input border shown while a request is in flight.'), true);

export const chatInputWorkingBorderColor3 = registerColor(
'chat.inputWorkingBorderColor3',
{ dark: '#51a2ff', light: '#51a2ff', hcDark: '#51a2ff', hcLight: '#51a2ff' },
localize('chat.inputWorkingBorderColor3', 'Third color stop of the animated chat input border shown while a request is in flight.'), true);
Loading