Skip to content

Conversation

@rossmanko
Copy link
Contributor

@rossmanko rossmanko commented Oct 26, 2025

Summary by CodeRabbit

  • New Features

    • Rate limit warnings now appear in the chat input and message area with remaining uses and reset time.
    • Dismiss action is remembered across chats/sessions.
    • Free-plan users are shown an Upgrade option when limits are near.
  • Improvements

    • Warnings are emitted during streaming for prompt visibility.
    • Thresholds and provider selection tuned for more relevant, timely warnings.
    • Remaining counts and reset times are more accurate and consistently shown.

- Add real-time rate limit warning banner above chat input
- Show warning when users have 10 (paid) or 5 (free) requests remaining
- Warning displays remaining count and reset time in user-friendly format
- Dismissible banner that persists across chat switches (global state)
- Live count updates as user continues sending messages
- Different message for zero remaining: "You've reached your [mode] limit"
- Upgrade button for free users with primary styling
- Clean UI matching chat input design (bg-input-chat, border styling)
- Backend sends rate limit info via SSE stream (transient data)
- Production-ready: no debug logs, fully typed, proper error handling
@vercel
Copy link

vercel bot commented Oct 26, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
hackerai Ready Ready Preview Comment Oct 27, 2025 1:17pm

@coderabbitai
Copy link

coderabbitai bot commented Oct 26, 2025

Walkthrough

Client UI displays server-emitted rate-limit warnings in chat input/messages and lets users dismiss them; server checkRateLimit now returns RateLimitInfo and chat-handler emits a data-rate-limit-warning SSE event; global state persists dismissal; provider mapping adjusted.

Changes

Cohort / File(s) Summary
UI: Rate limit component & wiring
app/components/RateLimitWarning.tsx, app/components/ChatInput.tsx, app/components/chat.tsx
Added RateLimitWarning component; ChatInput and Messages/chat pass and render rateLimitWarning and onDismissRateLimitWarning; chat populates warning from SSE and supports dismiss flow.
Global state
app/contexts/GlobalState.tsx
Added hasUserDismissedRateLimitWarning: boolean and setHasUserDismissedRateLimitWarning to provider and GlobalStateType.
Server: rate-limit logic & streaming
lib/rate-limit.ts, lib/api/chat-handler.ts, lib/utils/stream-writer-utils.ts
checkRateLimit now returns RateLimitInfo { remaining, resetTime, limit }; chat handler captures rate-limit info, emits data-rate-limit-warning via new writeRateLimitWarning when thresholds met; threshold varies by subscription.
Types
types/chat.ts
Added exported RateLimitInfo type (remaining, resetTime, limit).
AI provider mapping
lib/ai/providers.ts
Updated provider target mappings for ask-model and ask-model-free.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant ChatUI as Chat UI
    participant Handler as Chat Handler
    participant RateLimit as Rate Limit
    participant Redis as Redis (opt)
    participant Writer as Stream Writer
    participant GlobalState as Global State

    User->>ChatUI: Submit message
    ChatUI->>Handler: Send request
    Handler->>RateLimit: checkRateLimit(userId, mode, subscription)
    alt Redis configured
        RateLimit->>Redis: query counters
        Redis-->>RateLimit: remaining, resetTime, limit
    else Redis not configured
        RateLimit-->>Handler: default RateLimitInfo
    end
    RateLimit-->>Handler: RateLimitInfo
    alt remaining <= threshold
        Handler->>Writer: writeRateLimitWarning({remaining, resetTime, mode, subscription})
        Writer-->>ChatUI: SSE event data-rate-limit-warning
    end
    Handler->>Writer: stream chat SSE
    Writer-->>ChatUI: SSE chunks
    ChatUI->>GlobalState: read hasUserDismissedRateLimitWarning
    alt not dismissed
        ChatUI->>ChatUI: render RateLimitWarning
        User->>ChatUI: click dismiss
        ChatUI->>GlobalState: setHasUserDismissedRateLimitWarning(true)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

  • Areas for extra attention:
    • lib/api/chat-handler.ts — placement of warning emission in SSE flow and provider selection changes.
    • lib/rate-limit.ts — signature change to return RateLimitInfo and fallback when Redis absent.
    • lib/utils/stream-writer-utils.ts — SSE payload shape and transient flag semantics.
    • app/components/chat.tsx — ref usage to avoid stale closures and correct dismissal propagation.

Possibly related PRs

Poem

🐰 I munched a bug and found a sign,

A gentle beep: "Your calls align."
I hop, I click, the banner's gone,
Or tap to grow the plan at dawn.
Tiny paws, big server cheer — hooray!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The pull request title "Daily branch 2025 10 26" is a generic date-based branch name that is completely unrelated to the actual changes in the changeset. The PR implements comprehensive rate limit warning functionality across the chat application, including new UI components (ChatInput and RateLimitWarning), state management (GlobalState), integration with the chat handler to emit warnings when users approach rate limits, and supporting utilities. None of this is reflected in the title, which appears to be an automated daily branch naming convention rather than a meaningful description of the changes. A teammate scanning the commit history would have no indication of what functionality this PR introduces. Replace the title with a clear, descriptive one that summarizes the main change. For example: "Add rate limit warning UI and handling across chat" or "Implement rate limit warnings with dismissible UI component". This will help reviewers and future maintainers quickly understand the primary purpose of the PR without requiring them to read the full changeset.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch daily-branch-2025-10-26

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1e27288 and ed375bc.

📒 Files selected for processing (1)
  • app/components/RateLimitWarning.tsx (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/components/RateLimitWarning.tsx

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

Copy link

@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

🧹 Nitpick comments (5)
types/chat.ts (1)

91-95: Unify rate‑limit types across server and SSE.

RateLimitInfo.resetTime is Date, but SSE payload uses a string. Define a dedicated payload type and use it in stream-writer-utils and emitters.

Apply this addition near these lines:

 export type RateLimitInfo = {
   remaining: number;
   resetTime: Date;
   limit: number;
 };
+
+// For SSE/UI payloads (wire format)
+export type RateLimitWarningPayload = {
+  remaining: number;
+  resetTime: string; // ISO string
+  limit: number;
+  mode: ChatMode;
+  subscription: SubscriptionTier;
+};
lib/utils/stream-writer-utils.ts (1)

68-83: Type the payload and mark event type as const.

Use a shared RateLimitWarningPayload to avoid drift and include limit.

-import type { ChatMode, SubscriptionTier } from "@/types";
+import type { RateLimitWarningPayload } from "@/types";
...
-export const writeRateLimitWarning = (
-  writer: StreamWriter,
-  data: {
-    remaining: number;
-    resetTime: string; // ISO date string
-    mode: ChatMode;
-    subscription: SubscriptionTier;
-  },
-): void => {
+export const writeRateLimitWarning = (
+  writer: StreamWriter,
+  data: RateLimitWarningPayload,
+): void => {
   writer.write({
-    type: "data-rate-limit-warning",
+    type: "data-rate-limit-warning" as const,
     data,
     transient: true,
   });
 };
app/contexts/GlobalState.tsx (1)

180-185: Persist dismissal across reloads.

Store in localStorage to avoid re-showing after refresh.

-] = useState(false);
+] = useState<boolean>(() => {
+  if (typeof window === "undefined") return false;
+  return window.localStorage.getItem("rlw-dismissed") === "1";
+});
+
+useEffect(() => {
+  if (typeof window !== "undefined") {
+    if (hasUserDismissedRateLimitWarning) {
+      window.localStorage.setItem("rlw-dismissed", "1");
+    } else {
+      window.localStorage.removeItem("rlw-dismissed");
+    }
+  }
+}, [hasUserDismissedRateLimitWarning]);
lib/api/chat-handler.ts (1)

185-197: Include limit in the warning payload.

UI can render “remaining/limit”; aligns with shared payload type.

 if (rateLimitInfo.remaining <= warningThreshold) {
   writeRateLimitWarning(writer, {
     remaining: rateLimitInfo.remaining,
     resetTime: rateLimitInfo.resetTime.toISOString(),
+    limit: rateLimitInfo.limit,
     mode,
     subscription,
   });
 }
app/components/chat.tsx (1)

581-586: Consider simplifying the ternary conversion.

The ternary rateLimitWarning ? rateLimitWarning : undefined is explicit but could be simplified to rateLimitWarning ?? undefined for brevity, since the prop type expects undefined rather than null.

Apply this diff if you prefer a more concise expression:

-                            rateLimitWarning={
-                              rateLimitWarning ? rateLimitWarning : undefined
-                            }
+                            rateLimitWarning={rateLimitWarning ?? undefined}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ea42aa7 and e865db3.

📒 Files selected for processing (9)
  • app/components/ChatInput.tsx (4 hunks)
  • app/components/RateLimitWarning.tsx (1 hunks)
  • app/components/chat.tsx (8 hunks)
  • app/contexts/GlobalState.tsx (3 hunks)
  • lib/ai/providers.ts (1 hunks)
  • lib/api/chat-handler.ts (4 hunks)
  • lib/rate-limit.ts (3 hunks)
  • lib/utils/stream-writer-utils.ts (2 hunks)
  • types/chat.ts (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)

**/*.{ts,tsx}: Use Id from ./_generated/dataModel for document IDs (e.g., Id<'users'>) instead of string
Ensure Record key/value types align with validators (e.g., v.record(v.id('users'), v.string()) -> Record<Id<'users'>, string>)
Use as const on string literals in discriminated unions
When using Array type, explicitly declare arrays with const array: Array = [...]
When using Record type, explicitly declare with const record: Record<KeyType, ValueType> = {...}

Files:

  • lib/utils/stream-writer-utils.ts
  • lib/api/chat-handler.ts
  • types/chat.ts
  • lib/rate-limit.ts
  • lib/ai/providers.ts
  • app/contexts/GlobalState.tsx
  • app/components/chat.tsx
  • app/components/RateLimitWarning.tsx
  • app/components/ChatInput.tsx
🧬 Code graph analysis (6)
lib/utils/stream-writer-utils.ts (1)
types/chat.ts (2)
  • ChatMode (5-5)
  • SubscriptionTier (7-7)
lib/api/chat-handler.ts (2)
lib/rate-limit.ts (1)
  • checkRateLimit (7-126)
lib/utils/stream-writer-utils.ts (1)
  • writeRateLimitWarning (69-83)
lib/rate-limit.ts (1)
types/chat.ts (3)
  • ChatMode (5-5)
  • SubscriptionTier (7-7)
  • RateLimitInfo (91-95)
app/components/chat.tsx (2)
types/chat.ts (2)
  • ChatMode (5-5)
  • SubscriptionTier (7-7)
app/hooks/useLatestRef.ts (1)
  • useLatestRef (3-9)
app/components/RateLimitWarning.tsx (2)
types/chat.ts (2)
  • ChatMode (5-5)
  • SubscriptionTier (7-7)
app/hooks/usePricingDialog.ts (1)
  • redirectToPricing (68-70)
app/components/ChatInput.tsx (2)
types/chat.ts (2)
  • ChatMode (5-5)
  • SubscriptionTier (7-7)
app/components/RateLimitWarning.tsx (1)
  • RateLimitWarning (71-111)
🔇 Additional comments (12)
lib/utils/stream-writer-utils.ts (1)

4-4: LGTM. Importing types from "@/types" is consistent with the project’s barrel.

app/contexts/GlobalState.tsx (2)

91-94: LGTM. Interface surface for dismissal state is clear.


588-590: LGTM. Exposed via provider value.

lib/api/chat-handler.ts (1)

339-346: Both "price" and "latency" are valid values for providerOptions.openrouter.provider.sort according to OpenRouter documentation.

lib/ai/providers.ts (1)

10-10: Verify OpenRouter availability in your setup; consider env-configurable slug.

The model slug "qwen/qwen3-coder:exacto" is valid and documented by OpenRouter. However, Exacto endpoints route only to a curated subset of providers, so availability can vary by provider and region. Verify this model is available in your account/region before deployment. Additionally, making the slug env-configurable would enable fast rollback if needed.

app/components/RateLimitWarning.tsx (1)

71-111: LGTM!

The component implementation is clean and well-structured. The conditional messaging for zero vs. non-zero remaining interactions is appropriate, and the accessibility attributes are properly included.

app/components/chat.tsx (3)

89-92: Excellent use of ref to prevent stale closures in streaming callbacks.

Using useLatestRef for the dismissal flag ensures the onData callback always reads the current value, avoiding race conditions during streaming.


198-215: LGTM!

The rate limit warning integration correctly checks the dismissal flag before updating state and properly converts the ISO timestamp to a Date object.


423-427: LGTM!

The dismiss handler correctly clears the local warning state and persists the dismissal preference globally.

lib/rate-limit.ts (2)

7-23: LGTM!

The function signature update to return RateLimitInfo is appropriate for the warning system. The default values when Redis is not configured (999 remaining with a 5-hour window) effectively disable rate limiting while maintaining consistent return types.


107-113: LGTM!

The return value correctly constructs a RateLimitInfo object with all required fields: remaining from the rate limiter response, resetTime as a Date object, and limit from the calculated request limit.

app/components/ChatInput.tsx (1)

222-231: LGTM!

The RateLimitWarning integration is clean and correctly positioned before the Todo Panel. The conditional rendering appropriately checks for both the warning data and the dismiss handler before displaying the component.

- Replace broken day comparison that failed across month/year boundaries
- Add relative time display for durations < 6 hours (e.g., 'in 2h 30m')
- Use proper Date arithmetic for today/tomorrow checks with setDate()
- Handles all edge cases including month-end and year-end rollovers
…ours

- Remove unnecessary date/time logic for longer durations
- Only show relative time format (e.g., 'in 2h 30m')
- Simplifies code since max window is 5 hours
Copy link

@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

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2154d5f and 1e27288.

📒 Files selected for processing (1)
  • app/components/RateLimitWarning.tsx (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/convex_rules.mdc)

**/*.{ts,tsx}: Use Id from ./_generated/dataModel for document IDs (e.g., Id<'users'>) instead of string
Ensure Record key/value types align with validators (e.g., v.record(v.id('users'), v.string()) -> Record<Id<'users'>, string>)
Use as const on string literals in discriminated unions
When using Array type, explicitly declare arrays with const array: Array = [...]
When using Record type, explicitly declare with const record: Record<KeyType, ValueType> = {...}

Files:

  • app/components/RateLimitWarning.tsx
🧬 Code graph analysis (1)
app/components/RateLimitWarning.tsx (2)
types/chat.ts (2)
  • ChatMode (5-5)
  • SubscriptionTier (7-7)
app/hooks/usePricingDialog.ts (1)
  • redirectToPricing (68-70)
🔇 Additional comments (2)
app/components/RateLimitWarning.tsx (2)

1-12: LGTM! Clean imports and well-typed interface.

The imports are appropriate and the RateLimitWarningProps interface is well-structured with proper type references.


50-73: LGTM! Well-structured and accessible UI.

The JSX rendering is clean with proper conditional logic, good accessibility (aria-label on dismiss button), and appropriate use of UI components.

@rossmanko rossmanko merged commit a69229e into main Oct 27, 2025
3 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Jan 18, 2026
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.

2 participants