33
44package com .microsoft .copilot .eclipse .ui .chat ;
55
6- import java .util .Locale ;
6+ import java .util .List ;
77
88import org .eclipse .swt .SWT ;
99import org .eclipse .swt .custom .StyledText ;
1818import org .eclipse .ui .ISharedImages ;
1919import org .eclipse .ui .PlatformUI ;
2020
21- import com .microsoft .copilot .eclipse .ui . UiConstants ;
22- import com .microsoft .copilot .eclipse .ui .i18n . Messages ;
21+ import com .microsoft .copilot .eclipse .core . lsp . protocol . quota . CopilotPlan ;
22+ import com .microsoft .copilot .eclipse .ui .chat . QuotaActions . QuotaAction ;
2323import com .microsoft .copilot .eclipse .ui .swt .CssConstants ;
2424import com .microsoft .copilot .eclipse .ui .utils .UiUtils ;
2525
2626/**
27- * Widget to display a message when the user has no quota.
27+ * Widget that displays a warning message under a chat turn, optionally followed by plan-driven action buttons sourced
28+ * from {@link QuotaActions#forPlan(CopilotPlan, boolean)}. Presentation-only: the caller decides the message and
29+ * whether to pass a plan.
2830 */
2931public class WarnWidget extends Composite {
3032 private int buttonLeftMargin ;
@@ -33,28 +35,31 @@ public class WarnWidget extends Composite {
3335 * Create the composite.
3436 *
3537 * @param parent the parent composite
36- * @param message the message to display
38+ * @param style the SWT style bits
39+ * @param message the message to display ({@code null} treated as empty)
40+ * @param userPlan the user's Copilot plan to render plan-driven action buttons, or {@code null} for no buttons
41+ * @param overageEnabled whether additional paid usage is already enabled for the user; switches the
42+ * "Enable Additional Usage" label to "Increase Budget"
3743 */
38- public WarnWidget (Composite parent , int style , String message , int code ) {
44+ public WarnWidget (Composite parent , int style , String message , CopilotPlan userPlan , boolean overageEnabled ) {
3945 super (parent , style | SWT .BORDER );
40- setLayout (new GridLayout (1 , true ));
46+ GridLayout outerLayout = new GridLayout (1 , true );
47+ outerLayout .verticalSpacing = 0 ;
48+ setLayout (outerLayout );
4149 setLayoutData (new GridData (SWT .FILL , SWT .NONE , true , false ));
4250
4351 buildWarnLabelWithIcon (message );
4452
45- // 402 = quota exceeded. The server bakes the recommended next steps into the message text itself
46- // (see copilot-language-server-internal fetch.ts), so we drive button visibility off of message
47- // content to keep parity with the IntelliJ UpgradeNotificationComponent#initTbb rendering. See:
48- // https://github.com/microsoft/copilot-client/blob/77f8f28e1a1e2efb51b6f92649bd9d085b8b64f5/lib/src/conversation/fetchPostProcessor.ts#L232-L248
49- if (code == 402 ) {
50- buildActionButtonsFromMessage (message );
53+ if (userPlan != null ) {
54+ buildActionButtons (userPlan , overageEnabled );
5155 }
5256 parent .layout ();
5357 }
5458
5559 private void buildWarnLabelWithIcon (String message ) {
5660 Composite composite = new Composite (this , SWT .NONE );
57- composite .setLayout (new GridLayout (2 , false ));
61+ GridLayout warnLayout = new GridLayout (2 , false );
62+ composite .setLayout (warnLayout );
5863 composite .setLayoutData (new GridData (SWT .LEFT , SWT .NONE , true , false ));
5964
6065 Label iconLabel = new Label (composite , SWT .TOP );
@@ -63,7 +68,8 @@ private void buildWarnLabelWithIcon(String message) {
6368 GridData iconGd = new GridData (SWT .LEFT , SWT .TOP , false , false );
6469 iconGd .verticalIndent = 4 ;
6570 iconLabel .setLayoutData (iconGd );
66- buttonLeftMargin = warnImage .getBounds ().width + iconGd .verticalIndent ;
71+ buttonLeftMargin = warnLayout .marginWidth + warnLayout .marginLeft + warnImage .getBounds ().width
72+ + warnLayout .horizontalSpacing ;
6773
6874 ChatMarkupViewer textLabel = new ChatMarkupViewer (composite , SWT .LEFT | SWT .WRAP );
6975 StyledText styledText = textLabel .getTextWidget ();
@@ -75,56 +81,31 @@ private void buildWarnLabelWithIcon(String message) {
7581 }
7682
7783 /**
78- * Render action buttons based on phrases present in the 402 message body, mirroring the IntelliJ
79- * {@code UpgradeNotificationComponent#initTbb} logic:
80- * <ul>
81- * <li>{@code "additional overage"} or {@code "additional usage"} → "Enable Additional Usage"
82- * (manage-overage URL)</li>
83- * <li>{@code "increase budget"} (when neither overage nor usage phrase is present) →
84- * "Increase Budget" (manage-overage URL)</li>
85- * <li>{@code "upgrade your plan"} or the legacy {@code "30-day free trial"} hint →
86- * "Upgrade Plan" (upgrade-plan URL)</li>
87- * </ul>
88- *
89- * <p>The overage button is shown as primary when present; the upgrade button is primary only when no
90- * overage button is rendered, matching the IntelliJ button styling.
84+ * Render plan-driven action buttons for a quota-exceeded warning, kept in sync with the quota {@link StaticBanner}.
9185 */
92- private void buildActionButtonsFromMessage (String message ) {
93- if (message == null ) {
94- return ;
95- }
96- String lower = message .toLowerCase (Locale .ROOT );
97- boolean enableAdditionalUsage = lower .contains ("additional overage" ) || lower .contains ("additional usage" );
98- boolean increaseBudget = !enableAdditionalUsage && lower .contains ("increase budget" );
99- boolean upgradePlan = lower .contains ("upgrade your plan" ) || lower .contains ("30-day free trial" );
100- if (!enableAdditionalUsage && !increaseBudget && !upgradePlan ) {
86+ private void buildActionButtons (CopilotPlan userPlan , boolean overageEnabled ) {
87+ List <QuotaAction > actions = QuotaActions .forPlan (userPlan , overageEnabled );
88+ if (actions .isEmpty ()) {
10189 return ;
10290 }
10391
104- Composite composite = new Composite (this , SWT .NONE );
10592 RowLayout layout = new RowLayout (SWT .HORIZONTAL );
10693 layout .marginLeft = this .buttonLeftMargin ; // Align with the message text
94+ layout .marginTop = 0 ;
10795 layout .spacing = 10 ;
96+
97+ Composite composite = new Composite (this , SWT .NONE );
10898 composite .setLayout (layout );
10999
110- boolean overageButtonShown = enableAdditionalUsage || increaseBudget ;
111- if (enableAdditionalUsage ) {
112- addActionButton (composite , Messages .menu_quota_enableAdditionalUsage ,
113- UiConstants .MANAGE_COPILOT_OVERAGE_URL , true );
114- } else if (increaseBudget ) {
115- addActionButton (composite , Messages .menu_quota_increaseBudget ,
116- UiConstants .MANAGE_COPILOT_OVERAGE_URL , true );
117- }
118- if (upgradePlan ) {
119- addActionButton (composite , Messages .menu_quota_upgradePlan ,
120- UiConstants .COPILOT_UPGRADE_PLAN_URL , !overageButtonShown );
100+ for (QuotaAction action : actions ) {
101+ addButton (composite , action .label (), action .tooltip (), action .url (), action .primary ());
121102 }
122103 }
123104
124- private static void addActionButton (Composite parent , String label , String link , boolean primary ) {
105+ private static void addButton (Composite parent , String label , String tooltip , String link , boolean primary ) {
125106 Button button = new Button (parent , SWT .PUSH );
126107 button .setText (label );
127- button .setToolTipText (label );
108+ button .setToolTipText (tooltip );
128109 button .addSelectionListener (new SelectionAdapter () {
129110 @ Override
130111 public void widgetSelected (org .eclipse .swt .events .SelectionEvent event ) {
0 commit comments