mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-02 06:14:15 +01:00
show animation around chat input while working (#311125)
* developer joy animation around input box * add back removed stuff * don't show border while stuff is working * better css * add color components and cleanup * progress border
This commit is contained in:
@@ -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",
|
||||
@@ -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",
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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."),
|
||||
},
|
||||
[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',
|
||||
|
||||
+2
-1
@@ -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)];
|
||||
|
||||
+2
-1
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) => {
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
get inputEditor(): ICodeEditor {
|
||||
return this.input.inputEditor;
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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')) {
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
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 {
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user