Skip to content

Comments

Add usage limits UI for copilot#1540

Open
xlight05 wants to merge 2 commits intowso2:release/bi-1.8.xfrom
xlight05:usage-limits
Open

Add usage limits UI for copilot#1540
xlight05 wants to merge 2 commits intowso2:release/bi-1.8.xfrom
xlight05:usage-limits

Conversation

@xlight05
Copy link
Contributor

@xlight05 xlight05 commented Feb 23, 2026

Purpose

$Subject

Goals

Describe the solutions that this feature/fix will introduce to resolve the problems described above

Approach

Describe how you are implementing the solutions. Include an animated GIF or screenshot if the change affects the UI (email documentation@wso2.com to review all UI text). Include a link to a Markdown file or Google doc if the feature write-up is too long to paste here.

UI Component Development

Specify the reason if following are not followed.

  • Added reusable UI components to the ui-toolkit. Follow the intructions when adding the componenent.
  • Use ui-toolkit components wherever possible. Run npm run storybook from the root directory to view current components.
  • Matches with the native VSCode look and feel.

Manage Icons

Specify the reason if following are not followed.

  • Added Icons to the font-wso2-vscode. Follow the instructions.

User stories

Summary of user stories addressed by this change>

Release note

Brief description of the new feature or bug fix as it will appear in the release notes

Documentation

Link(s) to product documentation that addresses the changes of this PR. If no doc impact, enter “N/A” plus brief explanation of why there’s no doc impact

Training

Link to the PR for changes to the training content in https://github.com/wso2/WSO2-Training, if applicable

Certification

Type “Sent” when you have provided new/updated certification questions, plus four answers for each question (correct answer highlighted in bold), based on this change. Certification questions/answers should be sent to certification@wso2.com and NOT pasted in this PR. If there is no impact on certification exams, type “N/A” and explain why.

Marketing

Link to drafts of marketing content that will describe and promote this feature, including product page changes, technical articles, blog posts, videos, etc., if applicable

Automation tests

  • Unit tests

    Code coverage information

  • Integration tests

    Details about the test cases and coverage

Security checks

Samples

Provide high-level details about the samples related to this feature

Related PRs

List any other related PRs

Migrations (if applicable)

Describe migration steps and platforms on which migration has been tested

Test environment

List all JDK versions, operating systems, databases, and browser/versions on which this feature/fix was tested

Learning

Describe the research phase and any blog posts, patterns, libraries, or add-ons you used to solve the problem.

Summary by CodeRabbit

  • New Features

    • Added AI usage tracking in the AI panel showing remaining percentage and reset time; UI updates dynamically and disables chat when limits are exceeded.
    • Chat footer and input now support a disabled state, preventing input and submission when usage is exceeded.
    • Added backend usage retrieval to power the usage display.
  • Bug Fixes

    • Shortened usage-limit error messages for clearer, more concise feedback.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 23, 2026

📝 Walkthrough

Walkthrough

Introduces a new getUsage RPC and related UsageResponse type, wires backend usage fetching through RPC manager/handler/client, shortens 429 error messages, and adds a disabled state to input/footer components to block input when usage is exceeded.

Changes

Cohort / File(s) Summary
Core RPC Types
workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/index.ts, workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/interfaces.ts, workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/rpc-type.ts
Add UsageResponse interface (remainingUsagePercentage, resetsIn) and new getUsage RPC type / API method returning Promise<UsageResponse | undefined>.
Extension: RPC Manager & Handler
workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts, workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-handler.ts, workspaces/ballerina/ballerina-extension/src/features/ai/utils/ai-client.ts, workspaces/ballerina/ballerina-extension/src/features/ai/utils/ai-utils.ts
Register getUsage handler; implement AiPanelRpcManager.getUsage() that fetches backend usage when appropriate; shorten 429 usage-limit messages returned by fetch/error helper.
RPC Client
workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/ai-panel/rpc-client.ts
Expose/import UsageResponse and implement AiPanelRpcClient.getUsage() to call RPC and return usage data.
UI: AI Chat & Footer/Input
workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx, workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/Footer/index.tsx, workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChatInput/index.tsx, workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChatInput/StyledInput.tsx, workspaces/ballerina/ballerina-extension/src/views/ai-panel/aiMachine.ts
Add usage state and fetching in AIChat; format/display remaining usage and reset timers; disable footer/input when usage exceeded; add disabled?: boolean prop flow; remove obsolete exported USER_CHECK_BACKEND_URL.

Sequence Diagram(s)

sequenceDiagram
    participant UI as AIChat Component
    participant RPCClient as AiPanelRpcClient
    participant Handler as RPC Handler
    participant Mgr as AiPanelRpcManager
    participant API as Backend API

    UI->>RPCClient: getUsage()
    RPCClient->>Handler: request getUsage
    Handler->>Mgr: rpcManager.getUsage()
    Mgr->>API: fetchWithAuth(BACKEND_URL + LLM_API_BASE_PATH + "/usage")
    API-->>Mgr: UsageResponse { remainingUsagePercentage, resetsIn }
    Mgr-->>Handler: UsageResponse | undefined
    Handler-->>RPCClient: UsageResponse | undefined
    RPCClient-->>UI: UsageResponse | undefined
    UI->>UI: update usage state, compute isUsageExceeded
    UI->>UI: render remaining usage and reset timer, disable input/footer if exceeded
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped to check the AI's pace,
Counting calls in every case,
When numbers drop and limits near,
The footer rests, no more to steer,
Timers tick — a carrot for next year.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is largely unpopulated, with only 'Purpose' containing a placeholder '$Subject' and all other required sections left blank or showing template prompts. Complete the description by filling in: Goals, Approach (with UI/UX details), User stories, Release note, Documentation, and key aspects like whether ui-toolkit components were used and security checks were performed.
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Add usage limits UI for copilot' directly matches the main change: introducing usage tracking and display UI that shows remaining usage, exceeded status, and reset times in the AI panel.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChatInput/StyledInput.tsx (1)

396-409: ⚠️ Potential issue | 🟡 Minor

Missing aria-disabled when disabled=true breaks screen reader semantics

The element has role="textbox" and aria-multiline="true", but when disabled=true, no ARIA state communicates the non-editable condition to assistive technologies. A screen reader will still announce this as an interactive text field.

♿ Proposed fix
 <StyledInput
     ref={divRef}
     contentEditable={!disabled}
     spellCheck="true"
     onInput={handleInput}
     onKeyDown={handleKeyDown}
     onPaste={handlePaste}
     onBlur={onBlur}
     suppressContentEditableWarning={true}
     role="textbox"
     aria-multiline="true"
+    aria-disabled={disabled || undefined}
     data-placeholder={placeholder}
 />
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChatInput/StyledInput.tsx`
around lines 396 - 409, The StyledInput element currently uses role="textbox"
and aria-multiline="true" but does not expose the disabled state to AT; update
the StyledInput rendered in AIChatInput to include an ARIA state reflecting the
prop by adding aria-disabled={disabled} (and ensure it aligns with the existing
contentEditable={!disabled} logic) so screen readers know when the textbox is
not interactive.
workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChatInput/index.tsx (1)

465-469: ⚠️ Potential issue | 🟡 Minor

handleKeyDown can trigger handleSend while disabled=true

contentEditable={false} prevents text input but does not prevent keyboard events. A user who clicks the disabled input area (giving it focus) and presses Enter will call handleSend() if inputValue.text is non-empty at the time disabled becomes true.

🐛 Proposed fix
-    } else if (event.key === "Enter" && !event.shiftKey && !isLoading) {
+    } else if (event.key === "Enter" && !event.shiftKey && !isLoading && !disabled) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChatInput/index.tsx`
around lines 465 - 469, The keydown handler (handleKeyDown) currently calls
handleSend when Enter is pressed even if the component is disabled; update
handleKeyDown to first check the disabled flag (and isLoading) before calling
handleSend — e.g., if disabled is true return early (or skip calling handleSend)
so Enter presses on a focused, contentEditable={false} element won't trigger
send; reference handleKeyDown, disabled, isLoading, handleSend, and
inputValue.text when locating the logic to change.
🧹 Nitpick comments (4)
workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChatInput/index.tsx (1)

575-594: Attach and command buttons remain interactive when disabled=true

The "Attach Context" (handleAttachClick) and "/" (insertTextAtCursor) buttons have no guard on disabled. When usage is exceeded, users can still open the file picker and accumulate attachments even though no message can be sent.

♻️ Proposed fix
                <ActionButton
                    title="Chat with Command"
-                    disabled={inputValue.text !== ""}
+                    disabled={inputValue.text !== "" || disabled}
                    onClick={() => {
                        inputRef.current?.insertTextAtCursor({ text: "/" });
                    }}
                >
                    /
                </ActionButton>
                ...
-                <ActionButton title="Attach Context" onClick={handleAttachClick}>
+                <ActionButton title="Attach Context" onClick={handleAttachClick} disabled={disabled}>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChatInput/index.tsx`
around lines 575 - 594, The attach and command ActionButton handlers lack checks
for the component's disabled state, letting users open the file picker or insert
"/" even when actions are disabled; update the ActionButton props and handlers
so they respect the same disabled flag used elsewhere (e.g., the
send/usage-disabled boolean): add disabled={disabled} to both ActionButton
instances, and add early-return guards in handleAttachClick (avoid
fileInputRef.current?.click()) and in the command onClick (avoid
inputRef.current?.insertTextAtCursor({ text: "/" })) when disabled is true; also
add a guard at the start of onAttachmentSelection to ignore file changes if
disabled to prevent accumulating attachments.
workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx (3)

232-250: formatResetsIn and formatResetsInExact are pure functions that should live at module scope.

Both helpers have no dependency on component state or props, yet they are re-created on every render. Moving them outside the component is zero-risk and avoids unnecessary allocations.

♻️ Proposed fix
+const formatResetsIn = (seconds: number): string => {
+    const days = Math.floor(seconds / 86400);
+    if (days >= 1) return `${days} day${days > 1 ? 's' : ''}`;
+    const hours = Math.floor(seconds / 3600);
+    if (hours >= 1) return `${hours} hour${hours > 1 ? 's' : ''}`;
+    const mins = Math.floor(seconds / 60);
+    return `${mins} min${mins > 1 ? 's' : ''}`;
+};
+
+const formatResetsInExact = (seconds: number): string => {
+    const days = Math.floor(seconds / 86400);
+    const hours = Math.floor((seconds % 86400) / 3600);
+    const mins = Math.floor((seconds % 3600) / 60);
+    const parts: string[] = [];
+    if (days > 0) parts.push(`${days} day${days > 1 ? 's' : ''}`);
+    if (hours > 0) parts.push(`${hours} hour${hours > 1 ? 's' : ''}`);
+    if (mins > 0) parts.push(`${mins} minute${mins > 1 ? 's' : ''}`);
+    return parts.length > 0 ? parts.join(', ') : 'less than a minute';
+};

 const AIChat: React.FC = () => {
     ...
-    const formatResetsIn = ...
-    const formatResetsInExact = ...
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx`
around lines 232 - 250, formatResetsIn and formatResetsInExact are pure helpers
currently declared inside the component causing them to be re-created every
render; move both function declarations to module scope (outside the component
function) so they are defined once and can be reused, update any references
inside the component to call the relocated functions (formatResetsIn,
formatResetsInExact), and ensure their TypeScript signatures and exports (if
needed) remain unchanged.

159-160: usage state type duplicates UsageResponse — import the interface directly.

The inline type { remainingUsagePercentage: number; resetsIn: number } is identical to the UsageResponse interface already exported from @wso2/ballerina-core. Any future change to the interface won't be caught here.

♻️ Proposed fix

Add to the @wso2/ballerina-core import block (e.g. alongside ApprovalOverlayState):

+    UsageResponse,

Then update the state declaration:

-    const [usage, setUsage] = useState<{ remainingUsagePercentage: number; resetsIn: number } | null>(null);
+    const [usage, setUsage] = useState<UsageResponse | null>(null);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx`
around lines 159 - 160, The inline type used for the usage state duplicates the
exported UsageResponse interface; update the import block that currently brings
in ApprovalOverlayState from "@wso2/ballerina-core" to also import
UsageResponse, then change the state declaration const [usage, setUsage] =
useState<...> to use useState<UsageResponse | null>(null) so the component uses
the shared interface (referencing usage, setUsage and UsageResponse).

267-267: useEffect missing rpcClient dependency — inconsistent with the rest of this file.

Every other effect in this component that calls into rpcClient includes it in the dependency array (e.g., lines 311, 325, 334, 343, 363). The empty [] here captures fetchUsage at mount time; if the RPC context ever changes the effect will silently skip the refresh.

♻️ Proposed fix
-    useEffect(() => { fetchUsage(); }, []);
+    useEffect(() => { fetchUsage(); }, [rpcClient]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx`
at line 267, The useEffect that calls fetchUsage in the AIChat component
currently has an empty dependency array and therefore captures the mounted
fetchUsage closure; change it to include rpcClient (and any other external
values used by fetchUsage) so the effect reruns when the RPC context changes —
locate the useEffect invocation that calls fetchUsage and update its dependency
array to include rpcClient (and ensure fetchUsage either is stable or recreated
from rpcClient), matching the pattern used by the other effects in this file.
ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ebf8a66 and b15b5e8.

📒 Files selected for processing (13)
  • workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/index.ts
  • workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/interfaces.ts
  • workspaces/ballerina/ballerina-core/src/rpc-types/ai-panel/rpc-type.ts
  • workspaces/ballerina/ballerina-extension/src/features/ai/utils/ai-client.ts
  • workspaces/ballerina/ballerina-extension/src/features/ai/utils/ai-utils.ts
  • workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-handler.ts
  • workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts
  • workspaces/ballerina/ballerina-extension/src/views/ai-panel/aiMachine.ts
  • workspaces/ballerina/ballerina-rpc-client/src/rpc-clients/ai-panel/rpc-client.ts
  • workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/Footer/index.tsx
  • workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx
  • workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChatInput/StyledInput.tsx
  • workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChatInput/index.tsx
💤 Files with no reviewable changes (1)
  • workspaces/ballerina/ballerina-extension/src/views/ai-panel/aiMachine.ts
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts`:
- Around line 706-723: In getUsage(), improve error visibility and runtime
validation: when fetchWithAuth returns a non-ok response (response &&
!response.ok) log a warning including response.status and response.statusText
(and optionally await response.text() for more detail) instead of silently
returning undefined; after parsing JSON, validate the payload fields expected by
UsageResponse (e.g., remainingUsagePercentage and resetsIn) by checking they
exist and are numbers (coerce/parseInt/parseFloat as needed) and return
undefined or a safe default if validation fails to avoid NaN in the UI;
reference the getUsage function, fetchWithAuth call, and
UsageResponse/remainingUsagePercentage/resetsIn symbols when making these edits.

In
`@workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx`:
- Around line 252-265: In fetchUsage, ensure RPC failures don't leave stale
state: in the catch block reset usage state by calling setUsage(null) and
setIsUsageExceeded(false) so a transient getUsage error won't permanently block
the user; also replace the magic literal 3 used in the comparison
(remainingUsagePercentage < 3) with a clearly named constant (e.g.
USAGE_EXCEEDED_THRESHOLD_PERCENT) defined near the top of the module and use
that constant in the setIsUsageExceeded check to make the threshold explicit;
reference function fetchUsage and the setters setUsage / setIsUsageExceeded plus
remainingUsagePercentage when making the changes.

---

Outside diff comments:
In
`@workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChatInput/index.tsx`:
- Around line 465-469: The keydown handler (handleKeyDown) currently calls
handleSend when Enter is pressed even if the component is disabled; update
handleKeyDown to first check the disabled flag (and isLoading) before calling
handleSend — e.g., if disabled is true return early (or skip calling handleSend)
so Enter presses on a focused, contentEditable={false} element won't trigger
send; reference handleKeyDown, disabled, isLoading, handleSend, and
inputValue.text when locating the logic to change.

In
`@workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChatInput/StyledInput.tsx`:
- Around line 396-409: The StyledInput element currently uses role="textbox" and
aria-multiline="true" but does not expose the disabled state to AT; update the
StyledInput rendered in AIChatInput to include an ARIA state reflecting the prop
by adding aria-disabled={disabled} (and ensure it aligns with the existing
contentEditable={!disabled} logic) so screen readers know when the textbox is
not interactive.

---

Nitpick comments:
In
`@workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx`:
- Around line 232-250: formatResetsIn and formatResetsInExact are pure helpers
currently declared inside the component causing them to be re-created every
render; move both function declarations to module scope (outside the component
function) so they are defined once and can be reused, update any references
inside the component to call the relocated functions (formatResetsIn,
formatResetsInExact), and ensure their TypeScript signatures and exports (if
needed) remain unchanged.
- Around line 159-160: The inline type used for the usage state duplicates the
exported UsageResponse interface; update the import block that currently brings
in ApprovalOverlayState from "@wso2/ballerina-core" to also import
UsageResponse, then change the state declaration const [usage, setUsage] =
useState<...> to use useState<UsageResponse | null>(null) so the component uses
the shared interface (referencing usage, setUsage and UsageResponse).
- Line 267: The useEffect that calls fetchUsage in the AIChat component
currently has an empty dependency array and therefore captures the mounted
fetchUsage closure; change it to include rpcClient (and any other external
values used by fetchUsage) so the effect reruns when the RPC context changes —
locate the useEffect invocation that calls fetchUsage and update its dependency
array to include rpcClient (and ensure fetchUsage either is stable or recreated
from rpcClient), matching the pattern used by the other effects in this file.

In
`@workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChatInput/index.tsx`:
- Around line 575-594: The attach and command ActionButton handlers lack checks
for the component's disabled state, letting users open the file picker or insert
"/" even when actions are disabled; update the ActionButton props and handlers
so they respect the same disabled flag used elsewhere (e.g., the
send/usage-disabled boolean): add disabled={disabled} to both ActionButton
instances, and add early-return guards in handleAttachClick (avoid
fileInputRef.current?.click()) and in the command onClick (avoid
inputRef.current?.insertTextAtCursor({ text: "/" })) when disabled is true; also
add a guard at the start of onAttachmentSelection to ignore file changes if
disabled to prevent accumulating attachments.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx (1)

807-849: ⚠️ Potential issue | 🟡 Minor

Add fetchUsage() call to the error handler to refresh usage state on 429 rate-limit responses.

The stop handler refreshes usage (line 774), but the error handler does not. If the backend emits an error event without a subsequent stop event—particularly for rate-limit (429) responses—isUsageExceeded remains stale, the footer stays enabled, and users can continue submitting requests that fail with 429.

Proposed fix
 } else if (type === "error") {
     // ... existing error handling ...
     setIsCodeLoading(false);
     setIsLoading(false);
     isErrorChunkReceivedRef.current = true;
+    fetchUsage();
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx`
around lines 807 - 849, The error handler for type === "error" must refresh
usage state like the stop handler does: call fetchUsage() after setting
isErrorChunkReceivedRef.current = true (and/or before calling
setIsLoading(false)/setIsCodeLoading(false)) to ensure isUsageExceeded is
updated on 429 responses; update the block inside the setMessages callback
(where newMessages[...] gets errorTemplate) to invoke fetchUsage() (reference
fetchUsage, setMessages, setIsCodeLoading, setIsLoading,
isErrorChunkReceivedRef, and SYSTEM_ERROR_SECRET) so the UI footer/usage state
is refreshed when an error event arrives.
♻️ Duplicate comments (1)
workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx (1)

92-93: Previous concerns fully addressed — LGTM.

USAGE_EXCEEDED_THRESHOLD_PERCENT constant is in place and the catch block in fetchUsage now resets both usage and isUsageExceeded to prevent permanent lockout on transient RPC failures.

Also applies to: 254-270

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx`
around lines 92 - 93, The review confirms USAGE_EXCEEDED_THRESHOLD_PERCENT and
the updated fetchUsage catch block that resets both usage and isUsageExceeded
are correct; apply the same fix to the duplicate code at lines 254-270 by
ensuring any other fetchUsage (or similarly named usage-fetching) implementation
uses the USAGE_EXCEEDED_THRESHOLD_PERCENT constant and that its catch handler
resets both usage and isUsageExceeded to safe defaults to avoid permanent
lockout on transient RPC failures.
🧹 Nitpick comments (2)
workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx (2)

234-252: Move formatResetsIn / formatResetsInExact to module scope.

Neither function captures any component state or props, yet they are redefined on every render. Moving them outside AIChat costs nothing and makes them easier to unit-test.

♻️ Proposed refactor
+const formatResetsIn = (seconds: number): string => {
+    const days = Math.floor(seconds / 86400);
+    if (days >= 1) return `${days} day${days > 1 ? 's' : ''}`;
+    const hours = Math.floor(seconds / 3600);
+    if (hours >= 1) return `${hours} hour${hours > 1 ? 's' : ''}`;
+    const mins = Math.floor(seconds / 60);
+    return `${mins} min${mins > 1 ? 's' : ''}`;
+};
+
+const formatResetsInExact = (seconds: number): string => {
+    const days = Math.floor(seconds / 86400);
+    const hours = Math.floor((seconds % 86400) / 3600);
+    const mins = Math.floor((seconds % 3600) / 60);
+    const parts: string[] = [];
+    if (days > 0) parts.push(`${days} day${days > 1 ? 's' : ''}`);
+    if (hours > 0) parts.push(`${hours} hour${hours > 1 ? 's' : ''}`);
+    if (mins > 0) parts.push(`${mins} minute${mins > 1 ? 's' : ''}`);
+    return parts.length > 0 ? parts.join(', ') : 'less than a minute';
+};
+
 const AIChat: React.FC = () => {
     ...
-    const formatResetsIn = (seconds: number): string => { ... };
-    const formatResetsInExact = (seconds: number): string => { ... };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx`
around lines 234 - 252, The helper functions formatResetsIn and
formatResetsInExact are defined inside the AIChat component and get re-created
on every render; move both functions to module scope (outside the AIChat
component) so they are defined once, remain pure (no component state/props), and
become easier to unit-test—update any internal references to call the
module-level formatResetsIn and formatResetsInExact and ensure their signatures
remain (seconds: number) => string.

161-163: Use UsageResponse type and derive isUsageExceeded to eliminate redundant state.

The inline type { remainingUsagePercentage: number; resetsIn: number } should be replaced with UsageResponse from @wso2/ballerina-core — they are identical in structure. More importantly, isUsageExceeded is purely derived from usage and only updated together in fetchUsage. Keeping it as separate state adds unnecessary bookkeeping.

♻️ Proposed refactor
-import { ..., UsageResponse } from "@wso2/ballerina-core";
+import {
+    ...,
+    UsageResponse,
+} from "@wso2/ballerina-core";
 ...
-const [usage, setUsage] = useState<{ remainingUsagePercentage: number; resetsIn: number } | null>(null);
-const [isUsageExceeded, setIsUsageExceeded] = useState(false);
+const [usage, setUsage] = useState<UsageResponse | null>(null);
+
+const isUsageExceeded = usage !== null
+    && usage.resetsIn !== -1
+    && usage.remainingUsagePercentage < USAGE_EXCEEDED_THRESHOLD_PERCENT;

Remove all setIsUsageExceeded(...) calls from fetchUsage.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx`
around lines 161 - 163, Replace the inline usage type with the shared
UsageResponse from `@wso2/ballerina-core` by changing the usage state declaration
to use UsageResponse | null, remove the separate isUsageExceeded state entirely,
and compute the boolean where needed (e.g., const isUsageExceeded = usage ?
usage.remainingUsagePercentage <= 0 : false) instead of storing it; update any
places that call setIsUsageExceeded (notably inside fetchUsage) to only call
setUsage and remove those setIsUsageExceeded calls so the derived value is
calculated from usage at render/usage-read sites.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx`:
- Around line 807-849: The error handler for type === "error" must refresh usage
state like the stop handler does: call fetchUsage() after setting
isErrorChunkReceivedRef.current = true (and/or before calling
setIsLoading(false)/setIsCodeLoading(false)) to ensure isUsageExceeded is
updated on 429 responses; update the block inside the setMessages callback
(where newMessages[...] gets errorTemplate) to invoke fetchUsage() (reference
fetchUsage, setMessages, setIsCodeLoading, setIsLoading,
isErrorChunkReceivedRef, and SYSTEM_ERROR_SECRET) so the UI footer/usage state
is refreshed when an error event arrives.

---

Duplicate comments:
In
`@workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx`:
- Around line 92-93: The review confirms USAGE_EXCEEDED_THRESHOLD_PERCENT and
the updated fetchUsage catch block that resets both usage and isUsageExceeded
are correct; apply the same fix to the duplicate code at lines 254-270 by
ensuring any other fetchUsage (or similarly named usage-fetching) implementation
uses the USAGE_EXCEEDED_THRESHOLD_PERCENT constant and that its catch handler
resets both usage and isUsageExceeded to safe defaults to avoid permanent
lockout on transient RPC failures.

---

Nitpick comments:
In
`@workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx`:
- Around line 234-252: The helper functions formatResetsIn and
formatResetsInExact are defined inside the AIChat component and get re-created
on every render; move both functions to module scope (outside the AIChat
component) so they are defined once, remain pure (no component state/props), and
become easier to unit-test—update any internal references to call the
module-level formatResetsIn and formatResetsInExact and ensure their signatures
remain (seconds: number) => string.
- Around line 161-163: Replace the inline usage type with the shared
UsageResponse from `@wso2/ballerina-core` by changing the usage state declaration
to use UsageResponse | null, remove the separate isUsageExceeded state entirely,
and compute the boolean where needed (e.g., const isUsageExceeded = usage ?
usage.remainingUsagePercentage <= 0 : false) instead of storing it; update any
places that call setIsUsageExceeded (notably inside fetchUsage) to only call
setUsage and remove those setIsUsageExceeded calls so the derived value is
calculated from usage at render/usage-read sites.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b15b5e8 and 002eae2.

📒 Files selected for processing (2)
  • workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts
  • workspaces/ballerina/ballerina-visualizer/src/views/AIPanel/components/AIChat/index.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • workspaces/ballerina/ballerina-extension/src/rpc-managers/ai-panel/rpc-manager.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant