Path: blob/main/src/vs/workbench/contrib/editTelemetry/browser/editStats/aiStatsStatusBar.ts
5263 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { n } from '../../../../../base/browser/dom.js';6import { ActionBar, IActionBarOptions, IActionOptions } from '../../../../../base/browser/ui/actionbar/actionbar.js';7import { IAction } from '../../../../../base/common/actions.js';8import { Codicon } from '../../../../../base/common/codicons.js';9import { createHotClass } from '../../../../../base/common/hotReloadHelpers.js';10import { Disposable, DisposableStore } from '../../../../../base/common/lifecycle.js';11import { autorun, derived, IObservable, observableValue } from '../../../../../base/common/observable.js';12import { ThemeIcon } from '../../../../../base/common/themables.js';13import { localize } from '../../../../../nls.js';14import { ICommandService } from '../../../../../platform/commands/common/commands.js';15import { nativeHoverDelegate } from '../../../../../platform/hover/browser/hover.js';16import { ITelemetryService } from '../../../../../platform/telemetry/common/telemetry.js';17import { IStatusbarService, StatusbarAlignment } from '../../../../services/statusbar/browser/statusbar.js';18import { AI_STATS_SETTING_ID } from '../settingIds.js';19import type { AiStatsFeature } from './aiStatsFeature.js';20import { ChartViewMode, createAiStatsChart, ISessionData } from './aiStatsChart.js';21import './media.css';2223export class AiStatsStatusBar extends Disposable {24public static readonly hot = createHotClass(this);2526constructor(27private readonly _aiStatsFeature: AiStatsFeature,28@IStatusbarService private readonly _statusbarService: IStatusbarService,29@ICommandService private readonly _commandService: ICommandService,30@ITelemetryService private readonly _telemetryService: ITelemetryService,31) {32super();3334this._register(autorun((reader) => {35const statusBarItem = this._createStatusBar().keepUpdated(reader.store);3637const store = this._register(new DisposableStore());3839reader.store.add(this._statusbarService.addEntry({40name: localize('inlineSuggestions', "Inline Suggestions"),41ariaLabel: localize('inlineSuggestionsStatusBar', "Inline suggestions status bar"),42text: '',43tooltip: {44element: async (_token) => {45this._sendHoverTelemetry();46store.clear();47const elem = createAiStatsHover({48data: this._aiStatsFeature,49onOpenSettings: () => openSettingsCommand({ ids: [AI_STATS_SETTING_ID] }).run(this._commandService),50});51return elem.keepUpdated(store).element;52},53markdownNotSupportedFallback: undefined,54},55content: statusBarItem.element,56}, 'aiStatsStatusBar', StatusbarAlignment.RIGHT, 100));57}));58}5960private _sendHoverTelemetry(): void {61this._telemetryService.publicLog2<{62aiRate: number;63}, {64owner: 'hediet';65comment: 'Fired when the AI stats status bar hover tooltip is shown';66aiRate: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'The current AI rate percentage' };67}>(68'aiStatsStatusBar.hover',69{70aiRate: this._aiStatsFeature.aiRate.get(),71}72);73}747576private _createStatusBar() {77return n.div({78style: {79height: '100%',80display: 'flex',81alignItems: 'center',82justifyContent: 'center',83marginLeft: '3px',84marginRight: '3px',85}86}, [87n.div(88{89class: 'ai-stats-status-bar',90style: {91display: 'flex',92flexDirection: 'column',9394width: 50,95height: 6,9697borderRadius: 6,98borderWidth: '1px',99borderStyle: 'solid',100}101},102[103n.div({104style: {105flex: 1,106107display: 'flex',108overflow: 'hidden',109110borderRadius: 6,111border: '1px solid transparent',112}113}, [114n.div({115style: {116width: this._aiStatsFeature.aiRate.map(v => `${v * 100}%`),117backgroundColor: 'currentColor',118}119})120])121]122)123]);124}125}126127export interface IAiStatsHoverData {128readonly aiRate: IObservable<number>;129readonly acceptedInlineSuggestionsToday: IObservable<number>;130readonly sessions: IObservable<readonly ISessionData[]>;131}132133export interface IAiStatsHoverOptions {134readonly data: IAiStatsHoverData;135readonly onOpenSettings?: () => void;136}137138export function createAiStatsHover(options: IAiStatsHoverOptions) {139const chartViewMode = observableValue<ChartViewMode>('chartViewMode', 'days');140const aiRatePercent = options.data.aiRate.map(r => `${Math.round(r * 100)}%`);141142const createToggleButton = (mode: ChartViewMode, tooltip: string, icon: ThemeIcon) => {143return derived(reader => {144const currentMode = chartViewMode.read(reader);145const isActive = currentMode === mode;146147return n.div({148class: ['chart-toggle-button', isActive ? 'active' : ''],149style: {150padding: '2px 4px',151borderRadius: '3px',152cursor: 'pointer',153display: 'flex',154alignItems: 'center',155justifyContent: 'center',156},157onclick: () => {158chartViewMode.set(mode, undefined);159},160title: tooltip,161}, [162n.div({163class: ThemeIcon.asClassName(icon),164style: { fontSize: '14px' }165})166]);167});168};169170return n.div({171class: 'ai-stats-status-bar',172}, [173n.div({174class: 'header',175style: {176minWidth: '280px',177}178},179[180n.div({ style: { flex: 1 } }, [localize('aiStatsStatusBarHeader', "AI Usage Statistics")]),181n.div({ style: { marginLeft: 'auto' } }, options.onOpenSettings182? actionBar([183{184action: {185id: 'aiStats.statusBar.settings',186label: '',187enabled: true,188run: options.onOpenSettings,189class: ThemeIcon.asClassName(Codicon.gear),190tooltip: localize('aiStats.statusBar.configure', "Configure")191},192options: { icon: true, label: false, hoverDelegate: nativeHoverDelegate }193}194])195: [])196]197),198199n.div({ style: { display: 'flex' } }, [200n.div({ style: { flex: 1, paddingRight: '4px' } }, [201localize('text1', "AI vs Typing Average: {0}", aiRatePercent.get()),202]),203]),204n.div({ style: { flex: 1, paddingRight: '4px' } }, [205localize('text2', "Accepted inline suggestions today: {0}", options.data.acceptedInlineSuggestionsToday.get()),206]),207208// Chart section209n.div({210style: {211marginTop: '8px',212borderTop: '1px solid var(--vscode-widget-border)',213paddingTop: '8px',214}215}, [216// Chart header with toggle217n.div({218class: 'header',219style: {220display: 'flex',221alignItems: 'center',222marginBottom: '4px',223}224}, [225n.div({ style: { flex: 1 } }, [226chartViewMode.map(mode =>227mode === 'days'228? localize('chartHeaderDays', "AI Rate by Day")229: localize('chartHeaderSessions', "AI Rate by Session")230)231]),232n.div({233class: 'chart-view-toggle',234style: { marginLeft: 'auto', display: 'flex', gap: '2px' }235}, [236createToggleButton('days', localize('viewByDays', "Days"), Codicon.calendar),237createToggleButton('sessions', localize('viewBySessions', "Sessions"), Codicon.listFlat),238])239]),240241// Chart container242derived(reader => {243const sessions = options.data.sessions.read(reader);244const viewMode = chartViewMode.read(reader);245return n.div({246ref: (container) => {247const chart = createAiStatsChart({248sessions,249viewMode,250});251container.appendChild(chart);252}253});254}),255]),256]);257}258259function actionBar(actions: { action: IAction; options: IActionOptions }[], options?: IActionBarOptions) {260return derived((_reader) => n.div({261class: [],262style: {263},264ref: elem => {265const actionBar = _reader.store.add(new ActionBar(elem, options));266for (const { action, options } of actions) {267actionBar.push(action, options);268}269}270}));271}272273class CommandWithArgs {274constructor(275public readonly commandId: string,276public readonly args: unknown[] = [],277) { }278279public run(commandService: ICommandService): void {280commandService.executeCommand(this.commandId, ...this.args);281}282}283284function openSettingsCommand(options: { ids?: string[] } = {}) {285return new CommandWithArgs('workbench.action.openSettings', [{286query: options.ids ? options.ids.map(id => `@id:${id}`).join(' ') : undefined,287}]);288}289290291