mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-08-11 20:15:32 +08:00
feat(server): per-user AI usage tracking, cost alerting, and endpoint hardening
Usage tracking: - new UsageEvent collection records every STT/TTS/LLM call per user with provider, model, volume (audio seconds, characters, tokens) and an estimated USD cost; self-hosted providers (Speaches/Piper) and cache hits record at $0 - pricing table for whisper-1, tts-1, gpt-5-nano & co. in server/utils/usage.ts - weekly KPI mail gains an "AI-Nutzung & Kosten" section: weekly and rolling 30-day cost, per-kind breakdown, top 5 users by cost - quota alert mail when rolling 30-day cost exceeds USAGE_ALERT_USD (default $5), at most once per calendar month (UsageAlertDelivery) Hardening: - /api/atc/say now requires an authenticated session (middleware exemption removed); useFlightLabAudio sends the bearer token - /api/service/tools/latency requires auth (was a public LLM endpoint) - per-user rate limits: PTT 20/min, say 60/min, latency 5/min - cron endpoints (waitlist-drip, weekly-kpi-report) require a shared secret via ?secret= or x-cron-secret (CRON_SECRET, falls back to KPI_CRON_SECRET); allowed with a warning while unset so existing deployments keep working - PTT records the actual transcribed audio duration for billing accuracy Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -7,6 +7,9 @@ import {normalize, TTS_MODEL, normalizeATC} from "../../utils/normalize";
|
||||
import { getServerRuntimeConfig } from "../../utils/runtimeConfig";
|
||||
import {request} from "node:http";
|
||||
import { TransmissionLog } from "../../models/TransmissionLog";
|
||||
import { requireUserSession } from "../../utils/auth";
|
||||
import { enforceRateLimit } from "../../utils/rateLimit";
|
||||
import { recordUsage } from "../../utils/usage";
|
||||
|
||||
|
||||
function outDir() {
|
||||
@@ -157,7 +160,12 @@ async function speachesTTS(
|
||||
return Buffer.from(arr);
|
||||
}
|
||||
|
||||
const SAY_RATE_LIMIT_PER_MINUTE = 60;
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = await requireUserSession(event);
|
||||
enforceRateLimit(event, 'atc-say', String(user._id), SAY_RATE_LIMIT_PER_MINUTE);
|
||||
|
||||
const runtimeConfig = getServerRuntimeConfig();
|
||||
const body = await readBody<{
|
||||
text?: string;
|
||||
@@ -177,8 +185,6 @@ export default defineEventHandler(async (event) => {
|
||||
preNormalized?: boolean;
|
||||
}>(event);
|
||||
|
||||
// const user = await requireUserSession(event);
|
||||
|
||||
const rawSessionId = typeof body?.sessionId === "string"
|
||||
? body.sessionId.trim()
|
||||
: "";
|
||||
@@ -348,9 +354,20 @@ export default defineEventHandler(async (event) => {
|
||||
}
|
||||
};
|
||||
|
||||
await recordUsage({
|
||||
user: String(user._id),
|
||||
sessionId,
|
||||
kind: 'tts',
|
||||
// Cache hits cost nothing regardless of which provider filled the cache.
|
||||
provider: cacheHit ? 'cache' : ttsProvider,
|
||||
model: modelUsed,
|
||||
endpoint: '/api/atc/say',
|
||||
characters: normalized.length,
|
||||
});
|
||||
|
||||
try {
|
||||
await TransmissionLog.create({
|
||||
// user: user._id,
|
||||
user: user._id,
|
||||
role: "atc",
|
||||
channel: "say",
|
||||
direction: "outgoing",
|
||||
|
||||
Reference in New Issue
Block a user