All checks were successful
Deploy to Production / Build and Deploy (push) Successful in 12s
- Sidebar: dynamic brand-accent colors, brainstorm section restyled - AI chat general: popup panel with expand/collapse, hides when contextual AI open - AI chat contextual: tabs reordered (Actions first), X close button, height fix - Settings: all tabs restyled, 6 new color presets (sage, terracotta, iron, etc.) - Global color cleanup: emerald/orange hardcoded → brand-accent dynamic - Brainstorm page: orange → brand-accent throughout - PageEntry animation component added to key pages - Floating AI button: bg-brand-accent instead of hardcoded black - i18n: all 15 locales updated with new AI/billing keys - Billing: freemium quota tracking, BYOK, stripe subscription scaffolding - Admin: integrated into new design - AGENTS.md + CLAUDE.md project rules added
46 lines
1.0 KiB
TypeScript
46 lines
1.0 KiB
TypeScript
import { redis } from './redis';
|
|
import { getRedisKey, parseRedisInt } from './quota-utils';
|
|
|
|
const TTL_SECONDS = 90 * 24 * 60 * 60;
|
|
|
|
export function trackFeatureUsage(
|
|
userId: string,
|
|
feature: string,
|
|
tokensUsed: number,
|
|
): void {
|
|
if (tokensUsed < 0) {
|
|
console.warn('[usage-tracker] Negative tokensUsed ignored:', tokensUsed);
|
|
return;
|
|
}
|
|
|
|
if (tokensUsed === 0) return;
|
|
|
|
const key = getRedisKey(userId, feature);
|
|
|
|
redis
|
|
.pipeline()
|
|
.incrbyfloat(`${key}:tokens`, tokensUsed)
|
|
.expire(`${key}:tokens`, TTL_SECONDS)
|
|
.exec()
|
|
.catch((err) => {
|
|
console.error('[usage-tracker] Failed to track token usage:', err);
|
|
});
|
|
}
|
|
|
|
export async function getUsageCounter(
|
|
userId: string,
|
|
feature: string,
|
|
): Promise<{ requests: number; tokens: number }> {
|
|
const key = getRedisKey(userId, feature);
|
|
|
|
const [requests, tokens] = await Promise.all([
|
|
redis.get(key),
|
|
redis.get(`${key}:tokens`),
|
|
]);
|
|
|
|
return {
|
|
requests: parseRedisInt(requests),
|
|
tokens: parseRedisInt(tokens),
|
|
};
|
|
}
|