Skip to content
Draft
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
2 changes: 2 additions & 0 deletions src/vs/sessions/LAYOUT.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,8 @@ The `NewChatView` input uses the control-tier corner radius for its send button,

`ChatView` mounts session input banners directly above the chat input. The CI failures banner uses the orange accent for the card border/icon and for the primary Fix Checks button background/border.

Voice mode uses the same compact segmented dictation/voice control and restrained audio-reactive input glow as the main chat surface. Instructional placeholder text stays static while live partial transcript text may pulse; the composer keeps the same keyboard, context-menu, and ARIA behavior as the shared control.

The shared chat input can show a transparent VS Code pet overlay above the composer. `/vscode-pet` toggles the persisted preference in active chats and the new-session composer. The state hooks for idle, sleeping, processing, confirmation, completion, and activation remain wired, but currently every state shows the same idle buddy: blue in Stable and green in Insiders/development builds. Active chats anchor the pet to the actual input row so confirmation and question widgets above it do not add spacing, while the new-session composer anchors it to its input-area wrapper. Cursor-tracked pupils render over eye-less derivative sprites so movement cannot expose the original baked-in eyes; the source PNG and GIF assets remain unchanged. Enabling makes the pet hop into place; disabling makes it duck away before its image source is unloaded. Both transitions are interruptible and skipped when reduced motion is enabled. Hovering the pet invites the user to show it some love and teases future interactions.

When a `ChatView` loads its chat model (`acquireOrLoadSession`), it surfaces progress on **its own** progress bar, pinned to the top of that grid leaf. This mirrors how each editor group owns its `ProgressBar` (see `EditorGroupView`): the bar is created by the leaf host `AbstractChatView`, wrapped in a `ScopedProgressIndicator` (reused from `vs/workbench`) with an always-active scope, and driven via `AbstractChatView.showProgressWhile(promise, delay)`. Concurrent loads in other visible sessions each show their own progress instead of competing for a single part-wide bar, and overlapping loads on the same leaf are joined by the indicator so the bar only hides once all have settled. A short delay avoids flashing the bar for fast (cached) loads.
Expand Down
33 changes: 7 additions & 26 deletions src/vs/sessions/contrib/chat/browser/media/voiceChatView.css
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,14 @@
color: var(--vscode-foreground);
}

.voice-transcript-overlay .partial {
.voice-transcript-overlay .partial,
.voice-transcript-overlay .hint {
color: var(--vscode-foreground);
opacity: 0.6;
font-style: italic;
}

.voice-transcript-overlay .partial {
animation: sessionsVoiceTextPulse 1.5s ease-in-out infinite;
}

Expand Down Expand Up @@ -104,39 +108,16 @@
white-space: pre-wrap;
}

/* Voice icon glow on the new-session composer toolbar. chat.css only targets the
chat widget's `.chat-input-container`; the composer uses `.new-chat-input-area`,
so mirror the blue-listening / purple-speaking glow here. `voice-active` /
`voice-listening` are toggled by setupVoiceInputDecorations. */
@keyframes sessionsVoiceIconGlow {
0%,
100% {
box-shadow: 0 0 4px var(--sessions-voice-icon-glow-color);
}

50% {
box-shadow: 0 0 9px var(--sessions-voice-icon-glow-color);
}
}

/* Blue when listening */
.new-chat-input-area.voice-active.voice-listening .action-label.codicon-voice-mode {
--sessions-voice-icon-glow-color: rgba(88, 166, 255, 0.65);
color: var(--vscode-charts-blue, #58a6ff) !important;
border-radius: 50%;
}

.monaco-workbench.monaco-enable-motion .new-chat-input-area.voice-active.voice-listening .action-label.codicon-voice-mode {
animation: sessionsVoiceIconGlow 1.4s ease-in-out infinite;
box-shadow: 0 0 3px rgba(88, 166, 255, 0.25);
}

/* Purple when speaking */
.new-chat-input-area.voice-active.voice-speaking .action-label.codicon-voice-mode {
--sessions-voice-icon-glow-color: rgba(163, 113, 247, 0.65);
color: var(--vscode-charts-purple, #a371f7) !important;
border-radius: 50%;
}

.monaco-workbench.monaco-enable-motion .new-chat-input-area.voice-active.voice-speaking .action-label.codicon-voice-mode {
animation: sessionsVoiceIconGlow 1.4s ease-in-out infinite;
box-shadow: 0 0 3px rgba(163, 113, 247, 0.25);
}
4 changes: 2 additions & 2 deletions src/vs/sessions/contrib/chat/browser/voiceInputDecorations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ export function setupVoiceInputDecorations(services: IVoiceInputDecorationsServi
transcriptOverlayNode.style.display = '';
transcriptOverlayNode.classList.remove('has-transcript');
transcriptOverlay.replaceChildren();
const hint = dom.$('span.partial');
const hint = dom.$('span.hint');
const kb = keybindingService.lookupKeybinding('agentsVoice.pushToTalk');
const kbLabel = kb?.getLabel();
hint.textContent = kbLabel
Expand All @@ -161,7 +161,7 @@ export function setupVoiceInputDecorations(services: IVoiceInputDecorationsServi
transcriptOverlayNode.style.display = '';
transcriptOverlayNode.classList.remove('has-transcript');
transcriptOverlay.replaceChildren();
const hint = dom.$('span.partial');
const hint = dom.$('span.hint');
const kb = keybindingService.lookupKeybinding('agentsVoice.pushToTalk');
const kbLabel = kb?.getLabel();
hint.textContent = kbLabel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1085,8 +1085,8 @@ export class AgentsVoiceWidget extends Disposable {
if (this._inputBoxMicBtn) {
const iconGlowActive = voiceState === 'listening' || voiceState === 'speaking';
if (iconGlowActive) {
const shadowSpread = 3 + intensity * 8;
const shadowAlpha = 0.2 + intensity * 0.45;
const shadowSpread = 2 + intensity * 4;
const shadowAlpha = 0.06 + intensity * 0.12;
const glowColor = `rgba(${voiceState === 'speaking' ? '163,113,247' : '88,166,255'},${shadowAlpha})`;
this._inputBoxMicBtn.style.boxShadow = `0 0 ${shadowSpread}px ${glowColor}`;
} else {
Expand All @@ -1096,9 +1096,9 @@ export class AgentsVoiceWidget extends Disposable {

// Classic layout glow div
this._glowDiv.style.display = '';
const baseOpacity = 0.15 + intensity * 0.4;
const baseOpacity = 0.06 + intensity * 0.12;
const r = (onboarding || voiceState === 'speaking') ? '163,113,247' : '88,166,255';
this._glowDiv.style.background = `radial-gradient(ellipse 40% 70% at 50% 0%, rgba(${r},${baseOpacity}) 0%, transparent 100%), radial-gradient(ellipse 70% 100% at 50% 0%, rgba(${r},${baseOpacity * 0.4}) 0%, transparent 100%)`;
this._glowDiv.style.background = `radial-gradient(ellipse 40% 70% at 50% 0%, rgba(${r},${baseOpacity}) 0%, transparent 100%)`;
};
this._animationFrameId = getWindow(this.container).requestAnimationFrame(animate);
}
Expand Down
30 changes: 8 additions & 22 deletions src/vs/workbench/contrib/chat/browser/voiceClient/voiceGlow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,32 +47,18 @@ export interface IVoiceGlowStyle {
}

/**
* Compute the glow border color and box-shadow. Blue while listening (flashier
* when the transcript is hidden) and purple while speaking. Connected-idle voice
* mode renders no glow and never reaches this function.
* Compute the glow border color and box-shadow. Blue while listening and purple
* while speaking. Connected-idle voice mode renders no glow and never reaches
* this function.
*/
export function computeVoiceGlowStyle(voiceState: VoiceGlowState, intensity: number, transcriptHidden: boolean): IVoiceGlowStyle {
// Blue when listening, purple when speaking.
const rgb = voiceState === 'speaking' ? '163,113,247' : '88,166,255';
const flashy = voiceState === 'listening' && transcriptHidden;
let borderAlpha: number;
let shadowSpread: number;
let shadowAlpha: number;
if (flashy) {
// Flashy audio-reactive glow while speaking with no transcript visible.
borderAlpha = 0.6 + intensity * 0.4;
shadowSpread = 6 + intensity * 20;
shadowAlpha = 0.25 + intensity * 0.55;
} else {
// Standard glow (transcript visible or TTS playback).
borderAlpha = 0.4 + intensity * 0.5;
shadowSpread = 4 + intensity * 12;
shadowAlpha = 0.15 + intensity * 0.35;
}
const emphasized = voiceState === 'listening' && transcriptHidden;
const borderAlpha = (emphasized ? 0.35 : 0.3) + intensity * (emphasized ? 0.25 : 0.2);
const shadowSpread = (emphasized ? 3 : 2) + intensity * (emphasized ? 6 : 4);
const shadowAlpha = (emphasized ? 0.08 : 0.06) + intensity * (emphasized ? 0.14 : 0.12);
Comment on lines +57 to +60
const borderColor = `rgba(${rgb},${borderAlpha})`;
const boxShadow = flashy
// Double-layer glow for extra presence when listening without transcript.
? `0 0 ${shadowSpread}px rgba(${rgb},${shadowAlpha}), 0 0 ${shadowSpread * 2}px rgba(${rgb},${shadowAlpha * 0.3}), inset 0 0 ${shadowSpread * 0.5}px rgba(${rgb},${shadowAlpha * 0.4})`
: `0 0 ${shadowSpread}px rgba(${rgb},${shadowAlpha}), inset 0 0 ${shadowSpread * 0.4}px rgba(${rgb},${shadowAlpha * 0.3})`;
const boxShadow = `0 0 ${shadowSpread}px rgba(${rgb},${shadowAlpha})`;
return { borderColor, boxShadow };
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,22 @@
color: var(--vscode-charts-purple, #a371f7);
}

.chat-voice-input-mode {
--segmented-icon-toggle-width: 50px;
--segmented-icon-toggle-cell-width: 24px;
--segmented-icon-toggle-single-width: 48px;
}

.chat-voice-input-mode-icon {
font-size: 16px;
font-size: var(--vscode-codiconFontSize-compact);
}

/* The Device EQ waveform lives in the voice cell and transforms per state. Thin bars = default (disconnected); thick bars = filled (connected). */
.chat-voice-input-mode-bars {
display: inline-flex;
align-items: center;
gap: 2px;
height: 16px;
height: 12px;
}

.chat-voice-input-mode-bar {
Expand All @@ -54,9 +60,9 @@

/* Device EQ silhouette: symmetric center-peak (matches Device EQ.svg). Bars stay thin in every state — connected reads via a darker color, not thicker bars. */
.chat-voice-input-mode-bar:nth-child(1) { height: 4px; }
.chat-voice-input-mode-bar:nth-child(2) { height: 8px; }
.chat-voice-input-mode-bar:nth-child(3) { height: 12px; }
.chat-voice-input-mode-bar:nth-child(4) { height: 8px; }
.chat-voice-input-mode-bar:nth-child(2) { height: 6px; }
.chat-voice-input-mode-bar:nth-child(3) { height: 10px; }
.chat-voice-input-mode-bar:nth-child(4) { height: 6px; }
.chat-voice-input-mode-bar:nth-child(5) { height: 4px; }

/* Hover while connected → preview disconnect: collapse to a short, even, "silent" row (no waveform, no motion). The height transition makes leaving the hover grow the bars smoothly back into the active waveform. */
Expand Down Expand Up @@ -100,5 +106,5 @@

@keyframes chat-voice-input-mode-eq {
0%, 100% { height: 3px; }
50% { height: 13px; }
50% { height: 11px; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -589,7 +589,7 @@ export class VoiceInputModeActionViewItem extends BaseActionViewItem {
sum += this._barData[Math.min(bins - 1, i * step + j)];
}
const intensity = Math.min(1, (sum / step) / 180);
const heightPx = 3 + intensity * 11;
const heightPx = 3 + intensity * 8;
// Disable the CSS keyframe fallback while we drive heights from live audio.
this._voiceBarEls[i].style.animation = 'none';
this._voiceBarEls[i].style.height = `${heightPx}px`;
Expand Down
23 changes: 2 additions & 21 deletions src/vs/workbench/contrib/chat/browser/widget/media/chat.css
Original file line number Diff line number Diff line change
Expand Up @@ -4520,37 +4520,18 @@ have to be updated for changes to the rules above, or to support more deeply nes
outline: none !important;
}

@keyframes chat-voice-icon-glow {
0%,
100% {
box-shadow: 0 0 4px var(--chat-voice-icon-glow-color);
}

50% {
box-shadow: 0 0 9px var(--chat-voice-icon-glow-color);
}
}

/* Voice mode: blue when listening */
.chat-input-container.voice-active.voice-listening .chat-input-toolbars .action-label.codicon-voice-mode {
--chat-voice-icon-glow-color: rgba(88, 166, 255, 0.65);
color: var(--vscode-charts-blue, #58a6ff) !important;
border-radius: 50%;
}

.monaco-workbench.monaco-enable-motion .chat-input-container.voice-active.voice-listening .chat-input-toolbars .action-label.codicon-voice-mode {
animation: chat-voice-icon-glow 1.4s ease-in-out infinite;
box-shadow: 0 0 3px rgba(88, 166, 255, 0.25);
}

/* Voice mode: purple when speaking */
.chat-input-container.voice-active.voice-speaking .chat-input-toolbars .action-label.codicon-voice-mode {
--chat-voice-icon-glow-color: rgba(163, 113, 247, 0.65);
color: var(--vscode-charts-purple, #a371f7) !important;
border-radius: 50%;
}

.monaco-workbench.monaco-enable-motion .chat-input-container.voice-active.voice-speaking .chat-input-toolbars .action-label.codicon-voice-mode {
animation: chat-voice-icon-glow 1.4s ease-in-out infinite;
box-shadow: 0 0 3px rgba(163, 113, 247, 0.25);
}

/* Voice mode: green disconnect button */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
transcriptOverlayNode.style.display = '';
transcriptOverlayNode.classList.remove('has-transcript');
transcriptOverlay.replaceChildren();
const hint = $('span.partial');
const hint = $('span.hint');
switch (simVersion) {
case 'handsFree':
hint.textContent = localize('voiceMode.simHint.handsFree', "Hands-free \u2014 just start talking");
Expand Down Expand Up @@ -678,7 +678,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
transcriptOverlayNode.style.display = '';
transcriptOverlayNode.classList.remove('has-transcript');
transcriptOverlay.replaceChildren();
const hint = $('span.partial');
const hint = $('span.hint');
const kb = this.keybindingService.lookupKeybinding('workbench.action.chat.voiceInputMode.holdToTalk')
?? this.keybindingService.lookupKeybinding('agentsVoice.pushToTalk');
const kbLabel = kb?.getLabel();
Expand All @@ -691,7 +691,7 @@ export class ChatViewPane extends ViewPane implements IViewWelcomeDelegate {
transcriptOverlayNode.style.display = '';
transcriptOverlayNode.classList.remove('has-transcript');
transcriptOverlay.replaceChildren();
const hint = $('span.partial');
const hint = $('span.hint');
const kb = this.keybindingService.lookupKeybinding('agentsVoice.pushToTalk');
const kbLabel = kb?.getLabel();
hint.textContent = kbLabel
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,14 @@
color: var(--vscode-foreground);
}

.voice-transcript-overlay .partial {
.voice-transcript-overlay .partial,
.voice-transcript-overlay .hint {
color: var(--vscode-foreground);
opacity: 0.6;
font-style: italic;
}

.voice-transcript-overlay .partial {
animation: voiceTextPulse 1.5s ease-in-out infinite;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ suite('VoiceGlow', () => {
boxShadow: listeningStyle.boxShadow,
},
{
borderColor: 'rgba(88,166,255,0.65)',
boxShadow: '0 0 10px rgba(88,166,255,0.32499999999999996), inset 0 0 4px rgba(88,166,255,0.09749999999999999)'
borderColor: 'rgba(88,166,255,0.4)',
boxShadow: '0 0 4px rgba(88,166,255,0.12)'
}
);
});
Expand Down
Loading