66import java .time .ZonedDateTime ;
77import java .time .format .DateTimeFormatter ;
88import java .time .format .DateTimeParseException ;
9+ import java .util .ArrayList ;
10+ import java .util .List ;
911import java .util .Locale ;
1012
1113import org .apache .commons .lang3 .StringUtils ;
2022import org .eclipse .osgi .util .NLS ;
2123import org .eclipse .swt .SWT ;
2224import org .eclipse .swt .graphics .Font ;
25+ import org .eclipse .swt .graphics .GC ;
2326import org .eclipse .swt .layout .GridData ;
2427import org .eclipse .swt .layout .GridLayout ;
2528import 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
0 commit comments