mirror of
https://github.com/microsoft/vscode.git
synced 2026-05-31 00:10:04 +08:00
Update
This commit is contained in:
@@ -3,10 +3,10 @@
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { ThemeIcon } from '../../../../base/common/themables.js';
|
||||
import { localize } from '../../../../nls.js';
|
||||
import { ChatEntitlement, IChatSentiment, IQuotaSnapshot } from '../../../../workbench/services/chat/common/chatEntitlementService.js';
|
||||
import { Codicon } from '../../base/common/codicons.js';
|
||||
import { ThemeIcon } from '../../base/common/themables.js';
|
||||
import { localize } from '../../nls.js';
|
||||
import { ChatEntitlement, IQuotaSnapshot } from '../../workbench/services/chat/common/chatEntitlementService.js';
|
||||
|
||||
export type AccountTitleBarStateSource = 'account' | 'copilot';
|
||||
export type AccountTitleBarStateKind = 'default' | 'accent' | 'warning' | 'prominent';
|
||||
@@ -16,7 +16,7 @@ export interface IAccountTitleBarStateContext {
|
||||
readonly accountName?: string;
|
||||
readonly accountProviderLabel?: string;
|
||||
readonly entitlement: ChatEntitlement;
|
||||
readonly sentiment: Pick<IChatSentiment, 'hidden' | 'disabled' | 'untrusted'>;
|
||||
readonly sentiment: { readonly hidden?: boolean; readonly disabled?: boolean; readonly untrusted?: boolean };
|
||||
readonly quotas: {
|
||||
readonly chat?: IQuotaSnapshot;
|
||||
readonly completions?: IQuotaSnapshot;
|
||||
@@ -91,7 +91,7 @@ export function getAccountTitleBarState(context: IAccountTitleBarStateContext):
|
||||
|
||||
function getCopilotPresentation(
|
||||
entitlement: ChatEntitlement,
|
||||
sentiment: Pick<IChatSentiment, 'hidden' | 'disabled' | 'untrusted'>,
|
||||
sentiment: { readonly hidden?: boolean; readonly disabled?: boolean; readonly untrusted?: boolean },
|
||||
quotas: { readonly chat?: IQuotaSnapshot; readonly completions?: IQuotaSnapshot }
|
||||
): IAccountTitleBarState | undefined {
|
||||
if (sentiment.hidden) {
|
||||
27
src/vs/sessions/browser/chatDashboardService.ts
Normal file
27
src/vs/sessions/browser/chatDashboardService.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DisposableStore } from '../../base/common/lifecycle.js';
|
||||
import { InstantiationType, registerSingleton } from '../../platform/instantiation/common/extensions.js';
|
||||
import { createDecorator } from '../../platform/instantiation/common/instantiation.js';
|
||||
|
||||
export const IChatDashboardService = createDecorator<IChatDashboardService>('chatDashboardService');
|
||||
|
||||
export interface IChatDashboardService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
/**
|
||||
* Creates a chat status dashboard element embedded in a container div.
|
||||
* Returns `undefined` if the dashboard is not available.
|
||||
*/
|
||||
createDashboardElement(store: DisposableStore): HTMLElement | undefined;
|
||||
}
|
||||
|
||||
class NullChatDashboardService implements IChatDashboardService {
|
||||
readonly _serviceBrand: undefined;
|
||||
createDashboardElement(): HTMLElement | undefined { return undefined; }
|
||||
}
|
||||
|
||||
registerSingleton(IChatDashboardService, NullChatDashboardService, InstantiationType.Delayed);
|
||||
@@ -5,8 +5,7 @@
|
||||
|
||||
import './mobileChatShell.css';
|
||||
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { $, addDisposableListener, append, disposableWindowInterval, EventType } from '../../../../base/browser/dom.js';
|
||||
import { mainWindow } from '../../../../base/browser/window.js';
|
||||
import { $, addDisposableListener, append, EventType } from '../../../../base/browser/dom.js';
|
||||
import { Emitter, Event } from '../../../../base/common/event.js';
|
||||
import { ThemeIcon } from '../../../../base/common/themables.js';
|
||||
import { Codicon } from '../../../../base/common/codicons.js';
|
||||
@@ -25,8 +24,8 @@ import { IsNewChatSessionContext } from '../../../common/contextkeys.js';
|
||||
import { SideBarVisibleContext } from '../../../../workbench/common/contextkeys.js';
|
||||
import { Menus } from '../../menus.js';
|
||||
import { ChatEntitlement, ChatEntitlementService, IChatEntitlementService } from '../../../../workbench/services/chat/common/chatEntitlementService.js';
|
||||
import { getAccountTitleBarState, getAccountProfileImageUrl, getAccountTitleBarBadgeKey } from '../../../contrib/accountMenu/browser/accountTitleBarState.js';
|
||||
import { ChatStatusDashboard } from '../../../../workbench/contrib/chat/browser/chatStatus/chatStatusDashboard.js';
|
||||
import { getAccountTitleBarState, getAccountProfileImageUrl, getAccountTitleBarBadgeKey } from '../../accountTitleBarState.js';
|
||||
import { IChatDashboardService } from '../../chatDashboardService.js';
|
||||
|
||||
/**
|
||||
* Mobile titlebar — prepended above the workbench grid on phone viewports
|
||||
@@ -34,8 +33,8 @@ import { ChatStatusDashboard } from '../../../../workbench/contrib/chat/browser/
|
||||
*
|
||||
* Layout (contextual right slot):
|
||||
*
|
||||
* - **In a chat session** → `[☰] [session title] [+]`
|
||||
* - **Welcome / new session** → `[☰] [host widget | title] [account]`
|
||||
* - **In a chat session** → `[toggle sidebar] [session title] [+]`
|
||||
* - **Welcome / new session** → `[toggle sidebar] [host widget | title] [account]`
|
||||
*
|
||||
* The center slot switches content based on whether the sessions welcome
|
||||
* (home/empty) screen is visible:
|
||||
@@ -94,13 +93,14 @@ export class MobileTitlebarPart extends Disposable {
|
||||
|
||||
constructor(
|
||||
parent: HTMLElement,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@ISessionsManagementService private readonly sessionsManagementService: ISessionsManagementService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IDefaultAccountService private readonly defaultAccountService: IDefaultAccountService,
|
||||
@IAuthenticationService private readonly authenticationService: IAuthenticationService,
|
||||
@IChatEntitlementService private readonly chatEntitlementService: ChatEntitlementService,
|
||||
@IMenuService private readonly menuService: IMenuService,
|
||||
@IChatDashboardService private readonly chatDashboardService: IChatDashboardService,
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -382,6 +382,7 @@ export class MobileTitlebarPart extends Disposable {
|
||||
panelStore.add({
|
||||
dispose: () => {
|
||||
this.isAccountMenuVisible = false;
|
||||
this.copilotDashboardStore.clear();
|
||||
this.renderAccountState();
|
||||
}
|
||||
});
|
||||
@@ -441,18 +442,10 @@ export class MobileTitlebarPart extends Disposable {
|
||||
const dashboardSection = append(content, $('div.mobile-account-sheet-section'));
|
||||
const store = new DisposableStore();
|
||||
this.copilotDashboardStore.value = store;
|
||||
const dashboardElement = ChatStatusDashboard.instantiateInContents(this.instantiationService, store, {
|
||||
disableInlineSuggestionsSettings: true,
|
||||
disableModelSelection: true,
|
||||
disableProviderOptions: true,
|
||||
disableCompletionsSnooze: true,
|
||||
});
|
||||
store.add(disposableWindowInterval(mainWindow, () => {
|
||||
if (!dashboardElement.isConnected) {
|
||||
store.dispose();
|
||||
}
|
||||
}, 2000));
|
||||
append(dashboardSection, dashboardElement);
|
||||
const dashboardElement = this.chatDashboardService.createDashboardElement(store);
|
||||
if (dashboardElement) {
|
||||
append(dashboardSection, dashboardElement);
|
||||
}
|
||||
}
|
||||
|
||||
// Actions list
|
||||
|
||||
@@ -34,16 +34,18 @@ import { IOpenerService } from '../../../../platform/opener/common/opener.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
import { isWindows, isMacintosh } from '../../../../base/common/platform.js';
|
||||
import { UpdateHoverWidget } from './updateHoverWidget.js';
|
||||
import { ChatEntitlementService, IChatEntitlementService } from '../../../../workbench/services/chat/common/chatEntitlementService.js';
|
||||
import { ChatEntitlement, ChatEntitlementService, IChatEntitlementService } from '../../../../workbench/services/chat/common/chatEntitlementService.js';
|
||||
import { ChatStatusDashboard } from '../../../../workbench/contrib/chat/browser/chatStatus/chatStatusDashboard.js';
|
||||
import { HoverPosition } from '../../../../base/browser/ui/hover/hoverWidget.js';
|
||||
import { ThemeIcon } from '../../../../base/common/themables.js';
|
||||
import { getAccountProfileImageUrl, getAccountTitleBarBadgeKey, getAccountTitleBarState } from './accountTitleBarState.js';
|
||||
import { getAccountProfileImageUrl, getAccountTitleBarBadgeKey, getAccountTitleBarState } from '../../../browser/accountTitleBarState.js';
|
||||
import { SessionsWelcomeVisibleContext } from '../../../common/contextkeys.js';
|
||||
import { IsAuxiliaryWindowContext } from '../../../../workbench/common/contextkeys.js';
|
||||
import { IAuthenticationAccessService } from '../../../../workbench/services/authentication/browser/authenticationAccessService.js';
|
||||
import { IAuthenticationUsageService } from '../../../../workbench/services/authentication/browser/authenticationUsageService.js';
|
||||
import { IAuthenticationService } from '../../../../workbench/services/authentication/common/authentication.js';
|
||||
import { IChatDashboardService } from '../../../browser/chatDashboardService.js';
|
||||
import { InstantiationType, registerSingleton } from '../../../../platform/instantiation/common/extensions.js';
|
||||
|
||||
// --- Account Menu Items --- //
|
||||
const AccountMenu = Menus.AccountMenu;
|
||||
@@ -827,3 +829,32 @@ class AccountWidgetContribution extends Disposable implements IWorkbenchContribu
|
||||
}
|
||||
|
||||
registerWorkbenchContribution2(AccountWidgetContribution.ID, AccountWidgetContribution, WorkbenchPhase.BlockRestore);
|
||||
|
||||
// --- Chat Dashboard Service (real implementation for mobile account sheet) --- //
|
||||
|
||||
class ChatDashboardServiceImpl implements IChatDashboardService {
|
||||
readonly _serviceBrand: undefined;
|
||||
|
||||
constructor(
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
) { }
|
||||
|
||||
createDashboardElement(store: DisposableStore): HTMLElement | undefined {
|
||||
const dashboardElement = ChatStatusDashboard.instantiateInContents(this.instantiationService, store, {
|
||||
disableInlineSuggestionsSettings: true,
|
||||
disableModelSelection: true,
|
||||
disableProviderOptions: true,
|
||||
disableCompletionsSnooze: true,
|
||||
});
|
||||
|
||||
store.add(disposableWindowInterval(mainWindow, () => {
|
||||
if (!dashboardElement.isConnected) {
|
||||
store.dispose();
|
||||
}
|
||||
}, 2000));
|
||||
|
||||
return dashboardElement;
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IChatDashboardService, ChatDashboardServiceImpl, InstantiationType.Delayed);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import assert from 'assert';
|
||||
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../base/test/common/utils.js';
|
||||
import { ChatEntitlement } from '../../../../../workbench/services/chat/common/chatEntitlementService.js';
|
||||
import { getAccountProfileImageUrl, getAccountTitleBarBadgeKey, getAccountTitleBarState, IAccountTitleBarStateContext } from '../../browser/accountTitleBarState.js';
|
||||
import { getAccountProfileImageUrl, getAccountTitleBarBadgeKey, getAccountTitleBarState, IAccountTitleBarStateContext } from '../../../../browser/accountTitleBarState.js';
|
||||
|
||||
suite('Sessions - Account Title Bar State', () => {
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@ import { ILogService } from '../../../../platform/log/common/log.js';
|
||||
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
|
||||
import { IProductService } from '../../../../platform/product/common/productService.js';
|
||||
import { isWeb } from '../../../../base/common/platform.js';
|
||||
import { ChatEntitlement, ChatEntitlementService, IChatEntitlementService } from '../../../../workbench/services/chat/common/chatEntitlementService.js';
|
||||
import { IDefaultAccountService } from '../../../../platform/defaultAccount/common/defaultAccount.js';
|
||||
import { IAuthenticationService } from '../../../../workbench/services/authentication/common/authentication.js';
|
||||
import { URI } from '../../../../base/common/uri.js';
|
||||
@@ -76,7 +75,6 @@ export class SessionsWalkthroughOverlay extends Disposable {
|
||||
constructor(
|
||||
container: HTMLElement,
|
||||
private readonly _isFirstLaunch: boolean,
|
||||
@IChatEntitlementService private readonly chatEntitlementService: ChatEntitlementService,
|
||||
@IDefaultAccountService private readonly defaultAccountService: IDefaultAccountService,
|
||||
@IAuthenticationService private readonly authenticationService: IAuthenticationService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import { isWeb } from '../../../../base/common/platform.js';
|
||||
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { autorun } from '../../../../base/common/observable.js';
|
||||
import { SessionsWelcomeVisibleContext } from '../../../common/contextkeys.js';
|
||||
import { localize2 } from '../../../../nls.js';
|
||||
import { ILogService } from '../../../../platform/log/common/log.js';
|
||||
@@ -19,6 +20,7 @@ import { IStorageService, StorageScope, StorageTarget } from '../../../../platfo
|
||||
import { IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
|
||||
import { IWorkbenchEnvironmentService } from '../../../../workbench/services/environment/common/environmentService.js';
|
||||
import { IAuthenticationService } from '../../../../workbench/services/authentication/common/authentication.js';
|
||||
import { ChatEntitlement, ChatEntitlementService, IChatEntitlementService } from '../../../../workbench/services/chat/common/chatEntitlementService.js';
|
||||
import { SessionsWalkthroughOverlay, WalkthroughOutcome } from './sessionsWalkthrough.js';
|
||||
import { WELCOME_COMPLETE_KEY } from '../../../common/welcome.js';
|
||||
|
||||
@@ -99,6 +101,7 @@ export class SessionsWelcomeContribution extends Disposable implements IWorkbenc
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService,
|
||||
@IAuthenticationService private readonly authenticationService: IAuthenticationService,
|
||||
@IChatEntitlementService private readonly chatEntitlementService: ChatEntitlementService,
|
||||
@ILogService private readonly logService: ILogService,
|
||||
) {
|
||||
super();
|
||||
@@ -188,10 +191,23 @@ export class SessionsWelcomeContribution extends Disposable implements IWorkbenc
|
||||
// the welcome completion marker is still set (session exists but is
|
||||
// unusable). Only triggers after the user has previously completed
|
||||
// sign-in — avoids firing during initial load.
|
||||
//
|
||||
// The first autorun evaluation is skipped to avoid a false positive:
|
||||
// if entitlement starts as non-Unknown (e.g. Unresolved from cache)
|
||||
// and then transitions to Unknown on the first network check, we
|
||||
// don't want to re-show the walkthrough during normal startup.
|
||||
let isFirstRun = true;
|
||||
let wasSignedIn = false;
|
||||
this._register(autorun(reader => {
|
||||
this.chatEntitlementService.entitlementObs.read(reader);
|
||||
const entitlement = this.chatEntitlementService.entitlement;
|
||||
if (isFirstRun) {
|
||||
isFirstRun = false;
|
||||
if (entitlement !== ChatEntitlement.Unknown) {
|
||||
wasSignedIn = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (entitlement !== ChatEntitlement.Unknown) {
|
||||
wasSignedIn = true;
|
||||
return;
|
||||
@@ -205,7 +221,7 @@ export class SessionsWelcomeContribution extends Disposable implements IWorkbenc
|
||||
}
|
||||
this.logService.info('[sessions welcome] Entitlement became Unknown on web (token expired), re-showing walkthrough');
|
||||
this.storageService.remove(WELCOME_COMPLETE_KEY, StorageScope.APPLICATION);
|
||||
this.showWalkthrough();
|
||||
this.showWalkthrough(false);
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@@ -334,9 +334,9 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
this.logService.error('[DefaultAccount] Error while waiting for installed extensions to be registered', getErrorMessage(error));
|
||||
}
|
||||
|
||||
console.log('[DefaultAccount] Starting initialization');
|
||||
this.logService.debug('[DefaultAccount] Starting initialization');
|
||||
await this.doUpdateDefaultAccount();
|
||||
console.log('[DefaultAccount] Initialization complete. Account:', this._defaultAccount?.defaultAccount.accountName ?? 'null');
|
||||
this.logService.debug('[DefaultAccount] Initialization complete');
|
||||
|
||||
this._register(this.onDidChangeDefaultAccount(account => {
|
||||
this.telemetryService.publicLog2<DefaultAccountStatusTelemetry, DefaultAccountStatusTelemetryClassification>('defaultaccount:status', { status: account ? 'available' : 'unavailable', initial: false });
|
||||
@@ -434,7 +434,7 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
|
||||
const declaredProvider = this.authenticationService.declaredProviders.find(provider => provider.id === defaultAccountProvider.id);
|
||||
if (!declaredProvider) {
|
||||
console.log(`[DefaultAccount] Authentication provider is not declared.`, defaultAccountProvider);
|
||||
this.logService.info(`[DefaultAccount] Authentication provider is not declared.`, defaultAccountProvider);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -517,15 +517,13 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
|
||||
private async getDefaultAccountForAuthenticationProvider(authenticationProvider: IDefaultAccountAuthenticationProvider, options?: { forceRefresh?: boolean }): Promise<IDefaultAccountData | null> {
|
||||
try {
|
||||
console.log('[DefaultAccount] Getting Default Account from authenticated sessions for provider:', authenticationProvider.id);
|
||||
this.logService.debug('[DefaultAccount] Getting Default Account from authenticated sessions for provider:', authenticationProvider.id);
|
||||
const sessions = await this.findMatchingProviderSession(authenticationProvider.id, this.defaultAccountConfig.authenticationProvider.scopes);
|
||||
|
||||
if (!sessions?.length) {
|
||||
console.log('[DefaultAccount] No matching session found for provider:', authenticationProvider.id, 'Expected scopes:', JSON.stringify(this.defaultAccountConfig.authenticationProvider.scopes));
|
||||
this.logService.debug('[DefaultAccount] No matching session found for provider:', authenticationProvider.id);
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log('[DefaultAccount] Found', sessions.length, 'matching session(s). Account:', sessions[0].account.label, 'Scopes:', sessions[0].scopes);
|
||||
return this.getDefaultAccountFromAuthenticatedSessions(authenticationProvider, sessions, options);
|
||||
} catch (error) {
|
||||
this.logService.error('[DefaultAccount] Failed to get default account for provider:', authenticationProvider.id, getErrorMessage(error));
|
||||
@@ -588,25 +586,25 @@ class DefaultAccountProvider extends Disposable implements IDefaultAccountProvid
|
||||
|
||||
private async findMatchingProviderSession(authProviderId: string, allScopes: string[][]): Promise<AuthenticationSession[] | undefined> {
|
||||
const sessions = await this.getSessions(authProviderId);
|
||||
console.log('[DefaultAccount] Got', sessions.length, 'total session(s) for provider:', authProviderId);
|
||||
|
||||
// When no scopes are configured (e.g. vscode.dev where product
|
||||
// config may not include providerScopes), accept any session.
|
||||
if (allScopes.length === 0 && sessions.length > 0) {
|
||||
console.log('[DefaultAccount] No scopes configured, accepting all sessions');
|
||||
// When no scopes are configured on web (e.g. vscode.dev where
|
||||
// product config may not include providerScopes), accept any
|
||||
// session. This only applies to web — on desktop, scopes should
|
||||
// always be configured via product.json.
|
||||
if (isWeb && allScopes.length === 0 && sessions.length > 0) {
|
||||
this.logService.debug('[DefaultAccount] No scopes configured on web, accepting all sessions');
|
||||
return [...sessions];
|
||||
}
|
||||
|
||||
const matchingSessions = [];
|
||||
for (const session of sessions) {
|
||||
console.log('[DefaultAccount] Checking session', session.id, 'account:', session.account.label, 'scopes:', session.scopes);
|
||||
this.logService.debug('[DefaultAccount] Checking session with scopes', session.scopes);
|
||||
for (const scopes of allScopes) {
|
||||
if (this.scopesMatch(session.scopes, scopes)) {
|
||||
matchingSessions.push(session);
|
||||
}
|
||||
}
|
||||
}
|
||||
console.log('[DefaultAccount] Matching sessions:', matchingSessions.length);
|
||||
return matchingSessions.length > 0 ? matchingSessions : undefined;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user