The cooldown persists across page refreshes using localStorage:
User Experience
Before Cooldown
✅ Input fields are enabled
✅ Send button shows send icon
✅ User can type and submit messages
During Cooldown
❌ Input fields are disabled
⏱️ Send button shows countdown (e.g., "47s")
📝 Message displays: "Please wait X seconds before sending another message"
🔒 Cannot bypass by refreshing page
After Cooldown
✅ Input fields re-enable automatically
✅ Send button returns to normal
✅ User can submit new messages
Visual Indicators
Send Button States
Input Field States
Status Message
Configuration
Changing the Cooldown Duration
To modify the 50-second cooldown, update these locations in app/app/page.js:
Line 43-46: Cooldown check
Line 58: Initial cooldown setting
Line 46-49: Restoration check
Example: Change to 30 seconds
Replace all instances of 50 with 30.
Server-Side Alignment
The API route has a matching timeout in app/api/chat/route.js:
This should match or exceed your cooldown duration.
Implementation Details
localStorage Schema
Key: lastSubmitTime
Value: Timestamp in milliseconds (string)
Example:
Cleanup Strategy
localStorage is cleaned up:
When cooldown reaches 0
When expired timestamp is detected on mount
Never accumulates stale data
Security Considerations
Cannot Be Bypassed By:
✅ Refreshing the page - Timestamp persists in localStorage ✅ Opening new tab - Same localStorage across tabs ✅ Developer tools - Client-side only, expected behavior ✅ Clearing input - Cooldown tracks time, not input
Can Be Bypassed By:
⚠️ Clearing localStorage - Expected; user's choice ⚠️ Private/Incognito mode - Different storage context ⚠️ Different browser - Separate storage
Why Client-Side Only?
Simplicity: No backend state management needed
Scalability: No database or session storage required
Privacy: No tracking of user behavior
Fair usage: Prevents accidental spam, not malicious abuse
For production systems requiring stronger enforcement, consider:
Session-based rate limiting
IP-based rate limiting
Authentication-based quotas
Best Practices
For Users
Wait patiently - The AI needs time to perform web searches
Craft detailed questions - Make each query count
Use follow-ups wisely - Build on previous context
For Developers
Match server timeout - Keep maxDuration >= cooldown
Test edge cases - Verify refresh protection works
Communicate clearly - User message explains the wait
Consider UX - Show countdown for transparency
Troubleshooting
Cooldown Not Working
Symptoms: Can send multiple messages immediately
Solutions:
Check browser console for JavaScript errors
Verify localStorage is enabled
Ensure useEffect hooks are running
Check that state updates are occurring
Cooldown Stuck
Symptoms: Countdown never reaches 0
Solutions:
Refresh the page
Clear localStorage manually:
Check interval cleanup in useEffect
Cooldown Resets on Refresh
Symptoms: Refresh bypasses cooldown
Solutions:
Verify localStorage restoration useEffect exists
Check that 'lastSubmitTime' is being stored
Ensure parseInt is parsing correctly
Alternative Approaches
Session-Based
Pros: Server-enforced, more secure Cons: Requires backend state, less scalable
Token Bucket
Pros: Allows bursts, more flexible Cons: Complex implementation, needs backend
Future Enhancements
Potential improvements:
Variable cooldowns - Shorter for simple queries, longer for complex
User accounts - Track quotas per user
Premium tiers - Reduced/no cooldown for paid users
<input disabled={isLoading || cooldownRemaining > 0} placeholder="Ask about a token..." />
{
cooldownRemaining > 0 ? (
<span className="text-[#0f9d58]">
There is currently high traffic. Please wait {cooldownRemaining} seconds before sending
another message.
</span>
) : (
'Yuki402 provides AI-generated insights. Always do your own research.'
);
}
if (timeSinceLastSubmit < 50 && lastSubmitTime > 0) {
const remaining = Math.ceil(50 - timeSinceLastSubmit);
// Change both 50s
}
setCooldownRemaining(50); // Change this
if (timeSinceLastSubmit < 50) {
const remaining = Math.ceil(50 - timeSinceLastSubmit);
// Change both 50s
}
export const maxDuration = 50;
"1734567890123"
localStorage.removeItem('lastSubmitTime');
// Would require API session management
const session = await getSession();
if (session.lastMessageTime + 50000 > Date.now()) {
return { error: 'Rate limited' };
}
// Would require more complex implementation
const tokens = getUserTokens();
if (tokens > 0) {
consumeToken();
processMessage();
}