Spaces:
Running
Running
| const fs = require("fs"); | |
| const path = require("path"); | |
| const STATS_FILE = "/tmp/kimchi-stats.json"; | |
| const MAX_RECENT = 500; | |
| const MAX_ERRORS = 500; | |
| const MAX_LOGS = 500; | |
| const EST_COST_PER_1K_INPUT = 0.0006; | |
| const EST_COST_PER_1K_OUTPUT = 0.0024; | |
| let _stats = null; | |
| function load() { | |
| if (_stats) return _stats; | |
| try { | |
| if (fs.existsSync(STATS_FILE)) { | |
| const raw = fs.readFileSync(STATS_FILE, "utf-8"); | |
| _stats = JSON.parse(raw); | |
| _stats.keys = _stats.keys || { exhausted: [], throttled: [], errors: {} }; | |
| _stats.keys.exhausted = new Set(_stats.keys.exhausted); | |
| _stats.keys.throttled = new Set(_stats.keys.throttled); | |
| _stats.keys.errors = new Map(Object.entries(_stats.keys.errors || {})); | |
| return _stats; | |
| } | |
| } catch {} | |
| _stats = createFreshStats(); | |
| return _stats; | |
| } | |
| function createFreshStats() { | |
| return { | |
| totalRequests: 0, | |
| totalInputTokens: 0, | |
| totalOutputTokens: 0, | |
| totalErrors: 0, | |
| recentRequests: [], | |
| errors: [], | |
| logs: [], | |
| keys: { | |
| exhausted: new Set(), | |
| throttled: new Set(), | |
| errors: new Map(), | |
| }, | |
| }; | |
| } | |
| function save() { | |
| if (!_stats) return; | |
| try { | |
| const out = { | |
| ..._stats, | |
| keys: { | |
| exhausted: [..._stats.keys.exhausted], | |
| throttled: [..._stats.keys.throttled], | |
| errors: Object.fromEntries(_stats.keys.errors), | |
| }, | |
| }; | |
| fs.writeFileSync(STATS_FILE, JSON.stringify(out), "utf-8"); | |
| } catch {} | |
| } | |
| function markKeyExhausted(index) { | |
| const s = load(); | |
| s.keys.exhausted.add(index); | |
| save(); | |
| } | |
| function markKeyThrottled(index) { | |
| const s = load(); | |
| s.keys.throttled.add(index); | |
| save(); | |
| } | |
| function unmarkKeyThrottled(index) { | |
| const s = load(); | |
| s.keys.throttled.delete(index); | |
| save(); | |
| } | |
| function recordKeyError(index, error) { | |
| const s = load(); | |
| const key = `key_${index}`; | |
| const prev = s.keys.errors.get(key) || { count: 0, lastError: "", lastTime: 0 }; | |
| s.keys.errors.set(key, { | |
| count: prev.count + 1, | |
| lastError: error, | |
| lastTime: Date.now(), | |
| }); | |
| save(); | |
| } | |
| function logRequest(data) { | |
| const s = load(); | |
| s.totalRequests++; | |
| s.totalInputTokens += data.inputTokens || 0; | |
| s.totalOutputTokens += data.outputTokens || 0; | |
| if (data.status >= 400) { | |
| s.totalErrors++; | |
| } | |
| const entry = { | |
| id: s.totalRequests, | |
| model: data.model || "unknown", | |
| provider: data.provider || "kimchi", | |
| inputTokens: data.inputTokens || 0, | |
| outputTokens: data.outputTokens || 0, | |
| keyIndex: data.keyIndex ?? 0, | |
| status: data.status || 200, | |
| elapsed: data.elapsed || 0, | |
| error: data.error || null, | |
| timestamp: Date.now(), | |
| }; | |
| s.recentRequests.unshift(entry); | |
| if (s.recentRequests.length > MAX_RECENT) { | |
| s.recentRequests = s.recentRequests.slice(0, MAX_RECENT); | |
| } | |
| if (data.status >= 400 || data.error) { | |
| s.errors.unshift({ | |
| id: s.errors.length + 1, | |
| request_id: s.totalRequests, | |
| model: data.model, | |
| status: data.status, | |
| keyIndex: data.keyIndex, | |
| error: data.error || `HTTP ${data.status}`, | |
| details: data.details || null, | |
| timestamp: Date.now(), | |
| }); | |
| if (s.errors.length > MAX_ERRORS) { | |
| s.errors = s.errors.slice(0, MAX_ERRORS); | |
| } | |
| } | |
| s.logs.unshift({ | |
| id: s.logs.length + 1, | |
| level: data.status >= 400 ? "error" : "info", | |
| message: `${data.model} (${data.provider || "kimchi"}) in:${data.inputTokens || 0} out:${data.outputTokens || 0} ${data.elapsed}ms key:${data.keyIndex} status:${data.status}${data.error ? " err:" + data.error : ""}`, | |
| timestamp: Date.now(), | |
| }); | |
| if (s.logs.length > MAX_LOGS) { | |
| s.logs = s.logs.slice(0, MAX_LOGS); | |
| } | |
| save(); | |
| return entry; | |
| } | |
| function addLog(entry) { | |
| const s = load(); | |
| s.logs.unshift({ ...entry, id: s.logs.length + 1 }); | |
| if (s.logs.length > MAX_LOGS) { | |
| s.logs = s.logs.slice(0, MAX_LOGS); | |
| } | |
| save(); | |
| } | |
| function filterByRange(arr, range) { | |
| if (range === "all") return arr; | |
| const now = Date.now(); | |
| let since = 0; | |
| if (range === "today") { | |
| const d = new Date(); | |
| d.setHours(0, 0, 0, 0); | |
| since = d.getTime(); | |
| } else if (range === "week") { | |
| since = now - 7 * 24 * 60 * 60 * 1000; | |
| } else if (range === "month") { | |
| since = now - 30 * 24 * 60 * 60 * 1000; | |
| } | |
| return arr.filter((e) => e.timestamp >= since); | |
| } | |
| function getStats(range) { | |
| const s = load(); | |
| const filtered = filterByRange(s.recentRequests, range); | |
| const filteredErrors = filterByRange(s.errors, range); | |
| let totalIn = 0; | |
| let totalOut = 0; | |
| let totalReqs = 0; | |
| let totalErrs = 0; | |
| const providerStats = {}; | |
| for (const r of filtered) { | |
| totalReqs++; | |
| totalIn += r.inputTokens || 0; | |
| totalOut += r.outputTokens || 0; | |
| if (r.status >= 400) totalErrs++; | |
| const provider = r.provider || "kimchi"; | |
| if (!providerStats[provider]) { | |
| providerStats[provider] = { requests: 0, inputTokens: 0, outputTokens: 0, errors: 0 }; | |
| } | |
| providerStats[provider].requests++; | |
| providerStats[provider].inputTokens += r.inputTokens || 0; | |
| providerStats[provider].outputTokens += r.outputTokens || 0; | |
| if (r.status >= 400) providerStats[provider].errors++; | |
| } | |
| const cost = (totalIn / 1000) * EST_COST_PER_1K_INPUT + (totalOut / 1000) * EST_COST_PER_1K_OUTPUT; | |
| const keyErrors = {}; | |
| s.keys.errors.forEach((val, key) => { | |
| keyErrors[key] = val; | |
| }); | |
| const keysRaw = process.env.KIMCHI_API_KEYS || ""; | |
| const totalKeys = keysRaw ? keysRaw.split(/[,\s]+/).filter(Boolean).length : 0; | |
| return { | |
| range, | |
| totalRequests: totalReqs, | |
| totalInputTokens: totalIn, | |
| totalOutputTokens: totalOut, | |
| totalErrors: totalErrs, | |
| estimatedCost: cost, | |
| providers: providerStats, | |
| keys: { | |
| total: totalKeys, | |
| active: totalKeys - s.keys.exhausted.size, | |
| exhausted: s.keys.exhausted.size, | |
| throttled: s.keys.throttled.size, | |
| errors: keyErrors, | |
| }, | |
| recentRequests: filtered.slice(0, 100), | |
| errors: filteredErrors.slice(0, 50), | |
| logs: s.logs.slice(0, 150), | |
| }; | |
| } | |
| module.exports = { | |
| logRequest, | |
| addLog, | |
| getStats, | |
| markKeyExhausted, | |
| markKeyThrottled, | |
| unmarkKeyThrottled, | |
| recordKeyError, | |
| }; | |