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
2 changes: 2 additions & 0 deletions build/lib/stylelint/vscode-known-variables.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
"--vscode-agentSessionSelectedBadge-border",
"--vscode-agentSessionSelectedUnfocusedBadge-border",
"--vscode-agentStatusIndicator-background",
"--vscode-agentsVoice-speakingBackground",
"--vscode-agentsVoice-speakingForeground",
"--vscode-activeSessionView-background",
"--vscode-activeSessionView-foreground",
"--vscode-inactiveSessionView-background",
Expand Down
6 changes: 6 additions & 0 deletions src/vs/sessions/contrib/chat/browser/newChatInput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ import { ChatContextKeys } from '../../../../workbench/contrib/chat/common/actio
import { DictationDownloadRing, getDictationDownloadHoverMarkdown, getDictationPreparingLabel } from '../../../../workbench/contrib/chat/browser/speechToText/dictationDownloadRing.js';
import { IVoiceSessionController } from '../../../../workbench/contrib/chat/browser/voiceClient/voiceSessionController.js';
import { ChatPetWidget } from '../../../../workbench/contrib/chat/browser/widget/chatPetWidget.js';
import { IVoiceModeOnboardingService } from '../../../../workbench/contrib/agentsVoice/browser/voiceModeOnboarding.js';


const OPEN_OTEL_SETTINGS_COMMAND = 'github.copilot.chat.otel.openSettings';
Expand Down Expand Up @@ -378,6 +379,7 @@ export class NewChatInputWidget extends Disposable implements IHistoryNavigation
@IVoiceSessionController private readonly voiceSessionController: IVoiceSessionController,
@IVoiceInputModeService private readonly voiceInputModeService: IVoiceInputModeService,
@IAccessibilityService private readonly accessibilityService: IAccessibilityService,
@IVoiceModeOnboardingService private readonly voiceModeOnboardingService: IVoiceModeOnboardingService,
) {
super();
this._sessionModelSelectionModel = this._register(this.instantiationService.createInstance(SessionModelSelectionModel, this.options.session));
Expand Down Expand Up @@ -453,6 +455,10 @@ export class NewChatInputWidget extends Disposable implements IHistoryNavigation
));
notificationContainer.appendChild(notificationWidget.domNode);

// First-run Voice Mode introduction, docked above the input area
const voiceOnboardingContainer = dom.append(chatInputContainer, dom.$('.voice-mode-onboarding-container'));
this._register(this.voiceModeOnboardingService.registerHost(voiceOnboardingContainer, chatInputContainer, () => this.focus()));

// First-run dictation introduction, docked directly above the input area
// so it reads as one stack with it - the same slot the chat view uses.
const dictationOnboardingContainer = dom.append(chatInputContainer, dom.$('.dictation-onboarding-container'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { AgentsVoiceStorageKeys, AGENTS_VOICE_CONNECTED, AGENTS_VOICE_CONNECTING
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
import { IQuickInputService } from '../../../../platform/quickinput/common/quickInput.js';
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
import { INotificationService } from '../../../../platform/notification/common/notification.js';
import {
VoiceEnabledClassification, VoiceEnabledEvent,
VoiceDisabledClassification, VoiceDisabledEvent,
Expand All @@ -48,6 +49,8 @@ import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.j
import { ChatAgentLocation } from '../../chat/common/constants.js';
import { ICommandService } from '../../../../platform/commands/common/commands.js';
import { CONFIGURE_VOICE_INSTRUCTIONS_ACTION_ID } from '../../chat/browser/actions/configureVoiceInstructionsAction.js';
import { IVoiceModeOnboardingService } from './voiceModeOnboarding.js';
import { SHOW_VOICE_MODE_ONBOARDING_COMMAND } from '../../chat/browser/speechToText/micButtonMenuActions.js';

// --- Context Keys ---

Expand Down Expand Up @@ -113,6 +116,35 @@ class AgentsVoiceTelemetryContribution extends Disposable implements IWorkbenchC

registerWorkbenchContribution2(AgentsVoiceTelemetryContribution.ID, AgentsVoiceTelemetryContribution, WorkbenchPhase.AfterRestored);

// --- First-run introduction ---

/**
* Shows the Voice Mode introduction the first time a session starts. This
* watches the connection state rather than any one entry point, because Voice
* Mode can be started from the input-mode pill, a command, a keybinding or the
* Agents window - all of which land here.
*/
class AgentsVoiceOnboardingContribution extends Disposable implements IWorkbenchContribution {
static readonly ID = 'workbench.contrib.agentsVoiceOnboarding';

constructor(
@IVoiceSessionController voiceSessionController: IVoiceSessionController,
@IVoiceModeOnboardingService voiceModeOnboardingService: IVoiceModeOnboardingService,
) {
super();

this._register(autorun(reader => {
if (voiceSessionController.isConnecting.read(reader) || voiceSessionController.isConnected.read(reader)) {
voiceModeOnboardingService.showIfNeeded();
Comment thread
meganrogge marked this conversation as resolved.
}
}));
}
}

// Registered at the same late phase as the connected-key contribution so it
// does not force `IVoiceSessionController` to instantiate early.
registerWorkbenchContribution2(AgentsVoiceOnboardingContribution.ID, AgentsVoiceOnboardingContribution, WorkbenchPhase.Eventually);

// --- Voice mode button in Chat toolbar ---
// Shows the voice mode icon in both idle and active states.
// Click to connect if disconnected, or toggle PTT if connected.
Expand Down Expand Up @@ -379,6 +411,23 @@ registerAction2(class extends Action2 {
}
});

registerAction2(class extends Action2 {
constructor() {
super({
id: SHOW_VOICE_MODE_ONBOARDING_COMMAND,
title: nls.localize2('agentsVoice.showOnboarding', "Voice Mode: Show Introduction"),
f1: true,
precondition: ContextKeyExpr.equals('config.agents.voice.enabled', true),
});
}

run(accessor: ServicesAccessor): void {
if (!accessor.get(IVoiceModeOnboardingService).show()) {
accessor.get(INotificationService).info(nls.localize('agentsVoice.onboardingNeedsChat', "Open a chat to see the Voice Mode introduction."));
}
}
});

// --- Simulate Voice Connection (dev utility, backend down) ---

registerAction2(class extends Action2 {
Expand Down Expand Up @@ -408,6 +457,7 @@ registerAction2(class extends Action2 {
async run(accessor: ServicesAccessor): Promise<void> {
const storageService = accessor.get(IStorageService);
storageService.remove(AgentsVoiceStorageKeys.OnboardingCompleted, StorageScope.PROFILE);
storageService.remove(AgentsVoiceStorageKeys.IntroBannerShown, StorageScope.APPLICATION);
}
});

Expand Down
Loading
Loading