Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/browser/chatManagement/chatUsageWidget.ts
4780 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import './media/chatUsageWidget.css';
7
import { Disposable } from '../../../../../base/common/lifecycle.js';
8
import { Emitter } from '../../../../../base/common/event.js';
9
import * as DOM from '../../../../../base/browser/dom.js';
10
import { localize } from '../../../../../nls.js';
11
import { IChatEntitlementService, IQuotaSnapshot } from '../../../../services/chat/common/chatEntitlementService.js';
12
import { language } from '../../../../../base/common/platform.js';
13
import { safeIntl } from '../../../../../base/common/date.js';
14
15
const $ = DOM.$;
16
17
export class ChatUsageWidget extends Disposable {
18
19
private readonly _onDidChangeContentHeight = new Emitter<number>();
20
readonly onDidChangeContentHeight = this._onDidChangeContentHeight.event;
21
22
readonly element: HTMLElement;
23
private usageSection!: HTMLElement;
24
25
private readonly dateFormatter = safeIntl.DateTimeFormat(language, { year: 'numeric', month: 'long', day: 'numeric' });
26
private readonly dateTimeFormatter = safeIntl.DateTimeFormat(language, { year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' });
27
28
constructor(
29
@IChatEntitlementService private readonly chatEntitlementService: IChatEntitlementService
30
) {
31
super();
32
33
this.element = DOM.$('.chat-usage-widget');
34
this.create(this.element);
35
this.render();
36
37
// Update when quotas or entitlements change
38
this._register(this.chatEntitlementService.onDidChangeQuotaRemaining(() => this.render()));
39
this._register(this.chatEntitlementService.onDidChangeEntitlement(() => this.render()));
40
}
41
42
private create(container: HTMLElement): void {
43
// Content container
44
this.usageSection = DOM.append(container, $('.copilot-usage-section'));
45
}
46
47
private render(): void {
48
DOM.clearNode(this.usageSection);
49
50
const { chat: chatQuota, completions: completionsQuota, premiumChat: premiumChatQuota, resetDate, resetDateHasTime } = this.chatEntitlementService.quotas;
51
52
// Anonymous Indicator - show limited quotas
53
if (this.chatEntitlementService.anonymous && this.chatEntitlementService.sentiment.installed && !completionsQuota && !chatQuota && !premiumChatQuota) {
54
this.renderLimitedQuotaItem(this.usageSection, localize('completionsLabel', 'Inline Suggestions'));
55
this.renderLimitedQuotaItem(this.usageSection, localize('chatsLabel', 'Chat messages'));
56
}
57
// Copilot Usage section - show detailed breakdown of all quotas
58
else if (completionsQuota || chatQuota || premiumChatQuota) {
59
// Inline Suggestions
60
if (completionsQuota) {
61
this.renderQuotaItem(this.usageSection, localize('plan.inlineSuggestions', 'Inline Suggestions'), completionsQuota);
62
}
63
64
// Chat messages
65
if (chatQuota) {
66
this.renderQuotaItem(this.usageSection, localize('plan.chatMessages', 'Chat messages'), chatQuota);
67
}
68
69
// Premium requests
70
if (premiumChatQuota) {
71
this.renderQuotaItem(this.usageSection, localize('plan.premiumRequests', 'Premium requests'), premiumChatQuota);
72
73
// Additional overage message
74
if (premiumChatQuota.overageEnabled) {
75
const overageMessage = DOM.append(this.usageSection, $('.overage-message'));
76
overageMessage.textContent = localize('plan.additionalPaidEnabled', 'Additional paid premium requests enabled.');
77
}
78
}
79
80
// Reset date
81
if (resetDate) {
82
const resetText = DOM.append(this.usageSection, $('.allowance-resets'));
83
resetText.textContent = localize('plan.allowanceResets', 'Allowance resets {0}.', resetDateHasTime ? this.dateTimeFormatter.value.format(new Date(resetDate)) : this.dateFormatter.value.format(new Date(resetDate)));
84
}
85
}
86
87
// Emit height change
88
const height = this.element.offsetHeight || 400;
89
this._onDidChangeContentHeight.fire(height);
90
}
91
92
private renderQuotaItem(container: HTMLElement, label: string, quota: IQuotaSnapshot): void {
93
const quotaItem = DOM.append(container, $('.quota-item'));
94
95
const quotaItemHeader = DOM.append(quotaItem, $('.quota-item-header'));
96
const quotaItemLabel = DOM.append(quotaItemHeader, $('.quota-item-label'));
97
quotaItemLabel.textContent = label;
98
99
const quotaItemValue = DOM.append(quotaItemHeader, $('.quota-item-value'));
100
if (quota.unlimited) {
101
quotaItemValue.textContent = localize('plan.included', 'Included');
102
} else {
103
quotaItemValue.textContent = localize('plan.included', 'Included');
104
}
105
106
// Progress bar - using same structure as chat status
107
const progressBarContainer = DOM.append(quotaItem, $('.quota-bar'));
108
const progressBar = DOM.append(progressBarContainer, $('.quota-bit'));
109
const percentageUsed = this.getQuotaPercentageUsed(quota);
110
progressBar.style.width = percentageUsed + '%';
111
112
// Apply warning/error classes based on usage
113
if (percentageUsed >= 90) {
114
quotaItem.classList.add('error');
115
} else if (percentageUsed >= 75) {
116
quotaItem.classList.add('warning');
117
}
118
}
119
120
private getQuotaPercentageUsed(quota: IQuotaSnapshot): number {
121
if (quota.unlimited) {
122
return 0;
123
}
124
return Math.max(0, 100 - quota.percentRemaining);
125
}
126
127
private renderLimitedQuotaItem(container: HTMLElement, label: string): void {
128
const quotaItem = DOM.append(container, $('.quota-item'));
129
130
const quotaItemHeader = DOM.append(quotaItem, $('.quota-item-header'));
131
const quotaItemLabel = DOM.append(quotaItemHeader, $('.quota-item-label'));
132
quotaItemLabel.textContent = label;
133
134
const quotaItemValue = DOM.append(quotaItemHeader, $('.quota-item-value'));
135
quotaItemValue.textContent = localize('quotaLimited', 'Limited');
136
137
// Progress bar - using same structure as chat status
138
const progressBarContainer = DOM.append(quotaItem, $('.quota-bar'));
139
DOM.append(progressBarContainer, $('.quota-bit'));
140
}
141
}
142
143