Skip to content

Commit 5139738

Browse files
committed
Address comments.
1 parent 1480706 commit 5139738

7 files changed

Lines changed: 109 additions & 61 deletions

File tree

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/protocol/quota/CheckQuotaResult.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,17 @@ public class CheckQuotaResult {
1111
private Quota chat;
1212
private Quota completions;
1313
private Quota premiumInteractions;
14+
15+
/**
16+
* Quota usage for the 5-hour rolling interval (session limit). Only included for CFI (Copilot for Individuals)
17+
* token-based billing users on non-Max plans.
18+
*/
1419
private TbbQuota immediateUsageInterval;
20+
21+
/**
22+
* Quota usage for the 7-day rolling interval (weekly limit). Only included for CFI (Copilot for Individuals)
23+
* token-based billing users (all CFI plans).
24+
*/
1525
private TbbQuota extendedUsageInterval;
1626
private String resetDate;
1727
private String resetDateUtc;
@@ -101,8 +111,7 @@ public boolean equals(Object obj) {
101111
CheckQuotaResult other = (CheckQuotaResult) obj;
102112
return Objects.equals(chat, other.chat) && Objects.equals(completions, other.completions)
103113
&& copilotPlan == other.copilotPlan && Objects.equals(premiumInteractions, other.premiumInteractions)
104-
&& Objects.equals(resetDate, other.resetDate)
105-
&& Objects.equals(resetDateUtc, other.resetDateUtc)
114+
&& Objects.equals(resetDate, other.resetDate) && Objects.equals(resetDateUtc, other.resetDateUtc)
106115
&& Objects.equals(immediateUsageInterval, other.immediateUsageInterval)
107116
&& Objects.equals(extendedUsageInterval, other.extendedUsageInterval);
108117
}

com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/CopilotPreferenceInitializer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public void initializeDefaultPreferences() {
2929
pref.setDefault(Constants.CUSTOM_INSTRUCTIONS_WORKSPACE_ENABLED, false);
3030
pref.setDefault(Constants.CUSTOM_INSTRUCTIONS_WORKSPACE, "");
3131
pref.setDefault(Constants.AUTO_BREAKPOINT_RESPONSE, false);
32-
pref.setDefault(Constants.USAGE_ALERT_THRESHOLD, 90);
32+
pref.setDefault(Constants.USAGE_ALERT_THRESHOLD, 95);
3333
pref.setDefault(Constants.MCP, """
3434
{
3535
"servers": {

com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/Messages.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,11 @@ public class Messages extends NLS {
173173
public static String usage_status_no_monthly_limit;
174174
public static String usage_status_resets_in;
175175
public static String usage_status_resets_on_at;
176+
public static String usage_status_chat_messages;
177+
public static String usage_status_code_completions;
176178
public static String usage_status_alert_threshold;
179+
public static String usage_status_alert_chat_threshold;
180+
public static String usage_status_alert_session_threshold;
177181
public static String usage_status_duration_hours_mins;
178182
public static String usage_status_duration_hour_mins;
179183
public static String usage_status_duration_hours;

com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/UsageStatusPreferencePage.java

Lines changed: 70 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
import java.time.ZonedDateTime;
77
import java.time.format.DateTimeFormatter;
88
import java.time.format.DateTimeParseException;
9+
import java.util.ArrayList;
10+
import java.util.List;
911
import java.util.Locale;
1012

1113
import org.apache.commons.lang3.StringUtils;
@@ -20,6 +22,7 @@
2022
import org.eclipse.osgi.util.NLS;
2123
import org.eclipse.swt.SWT;
2224
import org.eclipse.swt.graphics.Font;
25+
import org.eclipse.swt.graphics.GC;
2326
import org.eclipse.swt.layout.GridData;
2427
import org.eclipse.swt.layout.GridLayout;
2528
import org.eclipse.swt.widgets.Button;
@@ -52,8 +55,8 @@ public class UsageStatusPreferencePage extends PreferencePage implements IWorkbe
5255

5356
private static final int ALIGNED_VERTICAL_SPACING = 8;
5457
private static final int ALIGNED_HORIZONTAL_SPACING = 16;
55-
private static final int DEFAULT_THRESHOLD = 90;
56-
private static final int[] THRESHOLD_VALUES = { 10, 20, 30, 40, 50, 60, 70, 80, 90 };
58+
private static final int DEFAULT_THRESHOLD = 95;
59+
private static final int[] THRESHOLD_VALUES = { 75, 80, 85, 90, 95 };
5760
private static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern("h:mm a", Locale.getDefault());
5861
private static final DateTimeFormatter DAY_FORMATTER = DateTimeFormatter.ofPattern("EEEE", Locale.getDefault());
5962
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("MMMM d", Locale.getDefault());
@@ -66,6 +69,7 @@ public class UsageStatusPreferencePage extends PreferencePage implements IWorkbe
6669
private Label accountLabel;
6770
private Label planValueLabel;
6871
private Combo thresholdCombo;
72+
private final List<CopilotUsageBar> usageBars = new ArrayList<>();
6973

7074
private enum PageLayout {
7175
FREE, INDIVIDUAL, CBCE_LIMITED, CBCE_UNLIMITED;
@@ -205,9 +209,7 @@ private void renderAccountGroup(Composite parent, String userName, String plan)
205209
planValueLabel = new Label(accountGroup, SWT.NONE);
206210
planValueLabel.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false));
207211
planValueLabel.setText(plan);
208-
Font boldFont = UiUtils.getBoldFont(planValueLabel.getDisplay(), planValueLabel.getFont());
209-
planValueLabel.setFont(boldFont);
210-
planValueLabel.addDisposeListener(e -> boldFont.dispose());
212+
applyBoldFont(planValueLabel);
211213
}
212214

213215
private void createUsageGroup(Composite parent) {
@@ -221,7 +223,7 @@ private void createUsageGroup(Composite parent) {
221223
usageGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false));
222224
}
223225

224-
private void createThresholdControls(Composite parent) {
226+
private void createThresholdControls(Composite parent, String alertText) {
225227
GridLayout thresholdLayout = new GridLayout(2, false);
226228
thresholdLayout.marginWidth = 0;
227229
thresholdLayout.marginHeight = 0;
@@ -231,7 +233,7 @@ private void createThresholdControls(Composite parent) {
231233
thresholdComposite.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false));
232234

233235
Label alertLabel = new Label(thresholdComposite, SWT.NONE);
234-
alertLabel.setText(Messages.usage_status_alert_threshold);
236+
alertLabel.setText(alertText);
235237

236238
thresholdCombo = new Combo(thresholdComposite, SWT.READ_ONLY);
237239
String[] thresholdLabels = new String[THRESHOLD_VALUES.length];
@@ -255,9 +257,7 @@ private void createLimitCompound(Composite parent, String title, String tooltip,
255257

256258
Label titleLabel = new Label(limitCompound, SWT.NONE);
257259
titleLabel.setText(title);
258-
Font titleBoldFont = UiUtils.getBoldFont(titleLabel.getDisplay(), titleLabel.getFont());
259-
titleLabel.setFont(titleBoldFont);
260-
titleLabel.addDisposeListener(e -> titleBoldFont.dispose());
260+
applyBoldFont(titleLabel);
261261
titleLabel.setLayoutData(new GridData(SWT.LEFT, SWT.NONE, false, false, 2, 1));
262262

263263
if (StringUtils.isNotBlank(tooltip)) {
@@ -282,10 +282,15 @@ private void createLimitCompound(Composite parent, String title, String tooltip,
282282
CopilotUsageBar usageBar = new CopilotUsageBar(limitCompound, SWT.NONE);
283283
usageBar.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false));
284284
usageBar.setPercentage(percentUsed);
285-
286-
Label percentLabel = new Label(limitCompound, SWT.NONE);
287-
percentLabel.setText(formatPercentUsed(percentUsed));
288-
percentLabel.setLayoutData(new GridData(SWT.END, SWT.CENTER, false, false));
285+
usageBars.add(usageBar);
286+
287+
Label percentLabel = new Label(limitCompound, SWT.RIGHT);
288+
percentLabel.setText(percentUsed + "%");
289+
GridData percentData = new GridData(SWT.END, SWT.CENTER, false, false);
290+
GC gc = new GC(percentLabel);
291+
percentData.widthHint = gc.textExtent("100%").x;
292+
gc.dispose();
293+
percentLabel.setLayoutData(percentData);
289294
}
290295

291296
private void createInfoCompound(Composite parent, String title, String message) {
@@ -299,9 +304,7 @@ private void createInfoCompound(Composite parent, String title, String message)
299304

300305
Label titleLabel = new Label(infoCompound, SWT.NONE);
301306
titleLabel.setText(title);
302-
Font titleBoldFont = UiUtils.getBoldFont(titleLabel.getDisplay(), titleLabel.getFont());
303-
titleLabel.setFont(titleBoldFont);
304-
titleLabel.addDisposeListener(e -> titleBoldFont.dispose());
307+
applyBoldFont(titleLabel);
305308

306309
Label messageLabel = new Label(infoCompound, SWT.WRAP);
307310
messageLabel.setLayoutData(new GridData(SWT.FILL, SWT.NONE, true, false));
@@ -351,20 +354,43 @@ private void renderUsageGroup(PageLayout layout, CheckQuotaResult quotaResult) {
351354
break;
352355
case CBCE_LIMITED:
353356
createLimitCompound(usageGroup, Messages.usage_status_monthly_limit, StringUtils.EMPTY,
354-
quotaResult.getPremiumInteractionsQuota(), formatMonthlyReset(quotaResult.getResetDateUtc()));
355-
createThresholdControls(usageGroup);
357+
quotaResult.getPremiumInteractionsQuota(), formatDateReset(quotaResult.getResetDateUtc(), DATE_FORMATTER));
358+
createThresholdControls(usageGroup, Messages.usage_status_alert_threshold);
356359
break;
357-
default: // FREE, INDIVIDUAL
360+
default: // CFI (Copilot for Individuals)
358361
TbbQuota immediate = quotaResult.getImmediateUsageInterval();
359362
TbbQuota extended = quotaResult.getExtendedUsageInterval();
360363

361-
createLimitCompound(usageGroup, Messages.usage_status_session_limit,
362-
Messages.usage_status_session_limit_description, immediate,
363-
formatSessionReset(immediate != null ? immediate.getResetAt() : null));
364-
createLimitCompound(usageGroup, Messages.usage_status_weekly_limit,
365-
Messages.usage_status_weekly_limit_description, extended,
366-
formatWeeklyReset(extended != null ? extended.getResetAt() : null));
367-
createThresholdControls(usageGroup);
364+
if (immediate == null && extended == null) {
365+
Quota chatQuota = quotaResult.getChatQuota();
366+
Quota completionsQuota = quotaResult.getCompletionsQuota();
367+
String monthlyReset = formatDateReset(quotaResult.getResetDateUtc(), DATE_FORMATTER);
368+
if (chatQuota != null) {
369+
createLimitCompound(usageGroup, Messages.usage_status_chat_messages, StringUtils.EMPTY, chatQuota,
370+
monthlyReset);
371+
}
372+
if (completionsQuota != null) {
373+
createLimitCompound(usageGroup, Messages.usage_status_code_completions, StringUtils.EMPTY, completionsQuota,
374+
monthlyReset);
375+
}
376+
} else {
377+
if (immediate != null) {
378+
createLimitCompound(usageGroup, Messages.usage_status_session_limit,
379+
Messages.usage_status_session_limit_description, immediate,
380+
formatSessionReset(immediate.getResetAt()));
381+
}
382+
if (extended != null) {
383+
createLimitCompound(usageGroup, Messages.usage_status_weekly_limit,
384+
Messages.usage_status_weekly_limit_description, extended,
385+
formatDateReset(extended.getResetAt(), DAY_FORMATTER));
386+
}
387+
}
388+
389+
// Show threshold controls for CFI users if we have any usage data
390+
String thresholdLabel = (immediate == null && extended == null)
391+
? Messages.usage_status_alert_chat_threshold
392+
: Messages.usage_status_alert_session_threshold;
393+
createThresholdControls(usageGroup, thresholdLabel);
368394
break;
369395
}
370396
}
@@ -379,7 +405,8 @@ private boolean hasUsageData(PageLayout layout, CheckQuotaResult quotaResult) {
379405
case CBCE_LIMITED:
380406
return quotaResult.getPremiumInteractionsQuota() != null;
381407
default:
382-
return quotaResult.getImmediateUsageInterval() != null && quotaResult.getExtendedUsageInterval() != null;
408+
return (quotaResult.getImmediateUsageInterval() != null && quotaResult.getExtendedUsageInterval() != null)
409+
|| quotaResult.getChatQuota() != null || quotaResult.getCompletionsQuota() != null;
383410
}
384411
}
385412

@@ -417,41 +444,27 @@ private String formatSessionReset(String utcTimestamp) {
417444
}
418445

419446
/**
420-
* Formats weekly reset as day-of-week + time, e.g. "Resets on Monday at 12:34 PM".
421-
*/
422-
private String formatWeeklyReset(String utcTimestamp) {
423-
if (StringUtils.isBlank(utcTimestamp) || EPOCH_TIMESTAMP.equals(utcTimestamp)) {
424-
return "";
425-
}
426-
try {
427-
ZonedDateTime localTime = Instant.parse(utcTimestamp).atZone(ZoneId.systemDefault());
428-
return NLS.bind(Messages.usage_status_resets_on_at, localTime.format(DAY_FORMATTER),
429-
localTime.format(TIME_FORMATTER));
430-
} catch (DateTimeParseException e) {
431-
CopilotCore.LOGGER.error("Failed to parse weekly reset date: " + utcTimestamp, e);
432-
return "";
433-
}
434-
}
435-
436-
/**
437-
* Formats monthly reset as month + day + time, e.g. "Resets on July 7 at 8:00 AM".
447+
* Formats a date reset as formatted date + time, e.g. "Resets on Monday at 12:34 PM" or "Resets on July 7 at 8:00
448+
* AM".
438449
*/
439-
private String formatMonthlyReset(String utcTimestamp) {
450+
private String formatDateReset(String utcTimestamp, DateTimeFormatter dateFormatter) {
440451
if (StringUtils.isBlank(utcTimestamp) || EPOCH_TIMESTAMP.equals(utcTimestamp)) {
441452
return "";
442453
}
443454
try {
444455
ZonedDateTime localTime = Instant.parse(utcTimestamp).atZone(ZoneId.systemDefault());
445-
return NLS.bind(Messages.usage_status_resets_on_at, localTime.format(DATE_FORMATTER),
456+
return NLS.bind(Messages.usage_status_resets_on_at, localTime.format(dateFormatter),
446457
localTime.format(TIME_FORMATTER));
447458
} catch (DateTimeParseException e) {
448-
CopilotCore.LOGGER.error("Failed to parse monthly reset date: " + utcTimestamp, e);
459+
CopilotCore.LOGGER.error("Failed to parse reset date: " + utcTimestamp, e);
449460
return "";
450461
}
451462
}
452463

453-
private static String formatPercentUsed(int percent) {
454-
return percent + "%";
464+
private static void applyBoldFont(Label label) {
465+
Font boldFont = UiUtils.getBoldFont(label.getDisplay(), label.getFont());
466+
label.setFont(boldFont);
467+
label.addDisposeListener(e -> boldFont.dispose());
455468
}
456469

457470
private int getThresholdIndex(int threshold) {
@@ -460,7 +473,7 @@ private int getThresholdIndex(int threshold) {
460473
return i;
461474
}
462475
}
463-
// Return index for default (90%)
476+
// Return index for default (95%)
464477
return THRESHOLD_VALUES.length - 1;
465478
}
466479

@@ -472,6 +485,11 @@ public boolean performOk() {
472485
getPreferenceStore().setValue(Constants.USAGE_ALERT_THRESHOLD, THRESHOLD_VALUES[selectedIndex]);
473486
}
474487
}
488+
for (CopilotUsageBar bar : usageBars) {
489+
if (!bar.isDisposed()) {
490+
bar.refreshBar();
491+
}
492+
}
475493
return super.performOk();
476494
}
477495

com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/preferences/messages.properties

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,11 @@ usage_status_not_signed_in=Sign in to GitHub to access usage status.
167167
usage_status_no_monthly_limit=You have no monthly limit on AI credit usage set by your organization.
168168
usage_status_resets_in=Resets in {0}
169169
usage_status_resets_on_at=Resets on {0} at {1}
170-
usage_status_alert_threshold=Alerts when usage hits threshold:
170+
usage_status_chat_messages=Chat Messages
171+
usage_status_code_completions=Code Completions
172+
usage_status_alert_threshold=Alert when usage hits threshold:
173+
usage_status_alert_chat_threshold=Alert me when chat usage reaches:
174+
usage_status_alert_session_threshold=Alert me when session usage reaches:
171175
usage_status_duration_hours_mins={0} hours {1} mins
172176
usage_status_duration_hour_mins=1 hour {0} mins
173177
usage_status_duration_hours={0} hours

com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/AbstractUsageBar.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,15 @@ protected int getPercentage() {
6666
return percentage;
6767
}
6868

69+
/**
70+
* Redraws the usage bar canvas, picking up any changes in fill/track colors.
71+
*/
72+
public void refreshBar() {
73+
if (!barCanvas.isDisposed()) {
74+
barCanvas.redraw();
75+
}
76+
}
77+
6978
/**
7079
* Returns the background track color for the unused portion of the bar.
7180
*/

com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/swt/CopilotUsageBar.java

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,22 @@
33
import org.eclipse.swt.graphics.Color;
44
import org.eclipse.swt.widgets.Composite;
55

6+
import com.microsoft.copilot.eclipse.core.Constants;
7+
import com.microsoft.copilot.eclipse.ui.CopilotUi;
8+
69
/**
710
* A usage bar colored for Copilot quota display:
811
* <ul>
912
* <li>Blue (#3574F0) when percentage &lt; 75%</li>
10-
* <li>Yellow (#FFB824) when 75% &le; percentage &lt; 90%</li>
11-
* <li>Red (#E05151) when percentage &ge; 90%</li>
13+
* <li>Yellow (#FFB824) when reaching user configured threshold (Constants.USAGE_ALERT_THRESHOLD) &le; percentage &lt;
14+
* 95%</li>
15+
* <li>Red (#E05151) when percentage &ge; 95%</li>
1216
* </ul>
1317
* The unused portion is rendered in gray (#DFE1E5).
1418
*/
1519
public class CopilotUsageBar extends AbstractUsageBar {
1620

17-
private static final int THRESHOLD_WARNING = 75;
18-
private static final int THRESHOLD_CRITICAL = 90;
21+
private static final int THRESHOLD_CRITICAL = 95;
1922

2023
private Color colorActive;
2124
private Color colorApproaching;
@@ -43,9 +46,10 @@ protected Color getTrackColor() {
4346

4447
@Override
4548
protected Color getFillColor() {
49+
int thresholdWarning = CopilotUi.getPlugin().getPreferenceStore().getInt(Constants.USAGE_ALERT_THRESHOLD);
4650
if (getPercentage() >= THRESHOLD_CRITICAL) {
4751
return colorExhausted;
48-
} else if (getPercentage() >= THRESHOLD_WARNING) {
52+
} else if (getPercentage() >= thresholdWarning) {
4953
return colorApproaching;
5054
}
5155
return colorActive;

0 commit comments

Comments
 (0)