Skip to content

Commit decc7a3

Browse files
committed
chore: rate limits from db
+ akashchat migration
1 parent 563c3dc commit decc7a3

3 files changed

Lines changed: 82 additions & 9 deletions

File tree

components/chat/chat-sidebar.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -706,13 +706,13 @@ export function ChatSidebar({
706706
/>
707707
</div>
708708

709-
<Link href="https://chatapi.akash.network" target="_blank" rel="noopener noreferrer">
709+
<Link href="https://akashml.com" target="_blank" rel="noopener noreferrer">
710710
<Button
711711
variant="ghost"
712712
className="w-full justify-start gap-2 h-8 px-2 text-sm font-light hover:bg-white dark:hover:bg-accent hover:text-accent-foreground"
713713
>
714714
<PlugZap className="w-3.5 h-3.5" />
715-
<span>AkashChat API</span>
715+
<span>API Access</span>
716716
</Button>
717717
</Link>
718718

components/models/model-detail-client.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,18 @@ export function ModelDetailClient({ modelId, model: serverModel, modelWithAccess
230230
</Button>
231231
)}
232232

233+
{/* API Access button */}
234+
<Button
235+
asChild
236+
className="w-full text-md flex items-center justify-center gap-2 py-6 hover:bg-border"
237+
variant="outline"
238+
>
239+
<Link href="https://akashml.com" target="_blank" rel="noopener noreferrer" aria-label="API Access">
240+
API Access
241+
<ArrowRight className="h-5 w-5 ml-1" />
242+
</Link>
243+
</Button>
244+
233245
{/* Status messages based on access control */}
234246
{modelWithAccess?.action_button === 'start_chat' && modelWithAccess?.action_text === 'Unavailable' && (
235247
<p className="text-sm text-muted-foreground mt-2 text-center">

lib/rate-limit.ts

Lines changed: 68 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getUserTier, getModelByModelId } from './database';
1+
import { getUserTier, getModelByModelId, type UserTier } from './database';
22
import redis from './redis';
33

44
const MAX_TOKENS = parseInt(process.env.RATE_LIMIT_ANONYMOUS_TOKENS || '25000');
@@ -23,18 +23,18 @@ export interface RateLimit {
2323
export function formatTimeUntilReset(resetTime: Date): string {
2424
const now = new Date();
2525
const diff = resetTime.getTime() - now.getTime();
26-
26+
2727
if (diff <= 0) {
2828
return 'now';
2929
}
30-
30+
3131
const hours = Math.floor(diff / (1000 * 60 * 60));
3232
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
33-
33+
3434
if (hours > 0) {
3535
return `${hours}h ${minutes}m`;
3636
}
37-
37+
3838
return `${minutes}m`;
3939
}
4040

@@ -44,6 +44,39 @@ export interface RateLimitConfig {
4444
keyPrefix?: string;
4545
}
4646

47+
// Cache for permissionless tier - initialized on first use
48+
let permissionlessTierCache: UserTier | null = null;
49+
let permissionlessTierCachePromise: Promise<UserTier | null> | null = null;
50+
51+
/**
52+
* Get cached permissionless tier from database
53+
*/
54+
async function getPermissionlessTier(): Promise<UserTier | null> {
55+
if (permissionlessTierCache) {
56+
return permissionlessTierCache;
57+
}
58+
59+
// If already fetching, return the existing promise
60+
if (permissionlessTierCachePromise) {
61+
return permissionlessTierCachePromise;
62+
}
63+
64+
// Start fetching and cache the promise
65+
permissionlessTierCachePromise = getUserTier(null)
66+
.then(tier => {
67+
permissionlessTierCache = tier;
68+
permissionlessTierCachePromise = null;
69+
return tier;
70+
})
71+
.catch(error => {
72+
console.error('Failed to cache permissionless tier:', error);
73+
permissionlessTierCachePromise = null;
74+
return null;
75+
});
76+
77+
return permissionlessTierCachePromise;
78+
}
79+
4780
export const DEFAULT_ANONYMOUS_LIMIT: RateLimitConfig = {
4881
maxTokens: MAX_TOKENS,
4982
windowMs: 4 * 60 * 60 * 1000, // 4 hours
@@ -72,22 +105,50 @@ export function getRateLimitConfig(isAuthenticated: boolean): RateLimitConfig {
72105
*/
73106
export async function getRateLimitConfigForUser(userId: string | null): Promise<RateLimitConfig> {
74107
if (!userId) {
108+
// Use cached permissionless tier from database
109+
const permissionlessTier = await getPermissionlessTier();
110+
if (permissionlessTier) {
111+
return {
112+
maxTokens: permissionlessTier.token_limit,
113+
windowMs: permissionlessTier.rate_limit_window_ms,
114+
keyPrefix: 'token_limit:anonymous:',
115+
};
116+
}
117+
// Fallback to env var if database fetch fails
75118
return DEFAULT_ANONYMOUS_LIMIT;
76119
}
77-
120+
78121
try {
79122
const userTier = await getUserTier(userId);
80123
if (!userTier) {
124+
// User has no tier, use permissionless tier
125+
const permissionlessTier = await getPermissionlessTier();
126+
if (permissionlessTier) {
127+
return {
128+
maxTokens: permissionlessTier.token_limit,
129+
windowMs: permissionlessTier.rate_limit_window_ms,
130+
keyPrefix: 'token_limit:user:',
131+
};
132+
}
81133
return DEFAULT_ANONYMOUS_LIMIT;
82134
}
83-
135+
84136
return {
85137
maxTokens: userTier.token_limit,
86138
windowMs: userTier.rate_limit_window_ms,
87139
keyPrefix: 'token_limit:user:',
88140
};
89141
} catch (error) {
90142
console.error('Error fetching user tier for rate limiting:', error);
143+
// Try to use cached permissionless tier as fallback
144+
const permissionlessTier = await getPermissionlessTier();
145+
if (permissionlessTier) {
146+
return {
147+
maxTokens: permissionlessTier.token_limit,
148+
windowMs: permissionlessTier.rate_limit_window_ms,
149+
keyPrefix: 'token_limit:user:',
150+
};
151+
}
91152
return DEFAULT_ANONYMOUS_LIMIT;
92153
}
93154
}

0 commit comments

Comments
 (0)