add search, chat, and feedback analytics with dashboard

This commit is contained in:
miloschwartz
2026-09-28 14:03:29 -04:00
parent 9d1b9f3820
commit 6588fc870b
42 changed files with 5932 additions and 108 deletions
+21 -17
View File
@@ -6,29 +6,33 @@ import 'server-only';
* reverse proxy's) in front if you run several replicas.
*/
const windowMs = 60_000;
const limit = Number(process.env.AI_RATE_LIMIT_PER_MINUTE ?? 10);
const hits = new Map<string, number[]>();
export function checkRateLimit(key: string): { ok: boolean; retryAfter: number } {
if (!Number.isFinite(limit) || limit <= 0) return { ok: true, retryAfter: 0 };
export function createRateLimiter(limit: number) {
const hits = new Map<string, number[]>();
const now = Date.now();
const recent = (hits.get(key) ?? []).filter((t) => now - t < windowMs);
if (recent.length >= limit) {
return function check(key: string): { ok: boolean; retryAfter: number } {
if (!Number.isFinite(limit) || limit <= 0) return { ok: true, retryAfter: 0 };
const now = Date.now();
const recent = (hits.get(key) ?? []).filter((t) => now - t < windowMs);
if (recent.length >= limit) {
hits.set(key, recent);
return { ok: false, retryAfter: Math.ceil((windowMs - (now - recent[0])) / 1000) };
}
recent.push(now);
hits.set(key, recent);
return { ok: false, retryAfter: Math.ceil((windowMs - (now - recent[0])) / 1000) };
}
recent.push(now);
hits.set(key, recent);
// keep the map from growing forever
if (hits.size > 10_000) {
for (const [k, v] of hits) if (v.every((t) => now - t >= windowMs)) hits.delete(k);
}
return { ok: true, retryAfter: 0 };
// keep the map from growing forever
if (hits.size > 10_000) {
for (const [k, v] of hits) if (v.every((t) => now - t >= windowMs)) hits.delete(k);
}
return { ok: true, retryAfter: 0 };
};
}
export const checkRateLimit = createRateLimiter(Number(process.env.AI_RATE_LIMIT_PER_MINUTE ?? 10));
export function clientKey(req: Request) {
return (
req.headers.get('x-forwarded-for')?.split(',')[0]?.trim() ||