mirror of
https://github.com/OpenSquawk/OpenSquawk
synced 2026-08-06 01:06:28 +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:
21
server/models/UsageAlertDelivery.ts
Normal file
21
server/models/UsageAlertDelivery.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
export interface UsageAlertDeliveryDocument extends mongoose.Document {
|
||||
monthKey: string
|
||||
thresholdUsd: number
|
||||
costUsd: number
|
||||
recipient: string
|
||||
sentAt: Date
|
||||
}
|
||||
|
||||
const usageAlertDeliverySchema = new mongoose.Schema<UsageAlertDeliveryDocument>({
|
||||
monthKey: { type: String, required: true, unique: true },
|
||||
thresholdUsd: { type: Number, required: true },
|
||||
costUsd: { type: Number, required: true },
|
||||
recipient: { type: String, required: true },
|
||||
sentAt: { type: Date, default: () => new Date() },
|
||||
})
|
||||
|
||||
export const UsageAlertDelivery =
|
||||
(mongoose.models.UsageAlertDelivery as mongoose.Model<UsageAlertDeliveryDocument> | undefined) ||
|
||||
mongoose.model<UsageAlertDeliveryDocument>('UsageAlertDelivery', usageAlertDeliverySchema)
|
||||
45
server/models/UsageEvent.ts
Normal file
45
server/models/UsageEvent.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
const { Schema } = mongoose
|
||||
|
||||
export type UsageKind = 'stt' | 'tts' | 'llm'
|
||||
export type UsageProvider = 'openai' | 'speaches' | 'piper' | 'cache'
|
||||
|
||||
export interface UsageEventDocument extends mongoose.Document {
|
||||
user?: mongoose.Types.ObjectId
|
||||
sessionId?: string
|
||||
kind: UsageKind
|
||||
provider: UsageProvider
|
||||
model: string
|
||||
endpoint: string
|
||||
/** Audio length for STT in seconds */
|
||||
audioSeconds?: number
|
||||
/** Synthesized text length for TTS */
|
||||
characters?: number
|
||||
inputTokens?: number
|
||||
outputTokens?: number
|
||||
/** Estimated cost in USD (0 for self-hosted providers and cache hits) */
|
||||
costUsd: number
|
||||
createdAt: Date
|
||||
}
|
||||
|
||||
const usageEventSchema = new mongoose.Schema<UsageEventDocument>({
|
||||
user: { type: Schema.Types.ObjectId, ref: 'User', index: true },
|
||||
sessionId: { type: String },
|
||||
kind: { type: String, enum: ['stt', 'tts', 'llm'], required: true },
|
||||
provider: { type: String, enum: ['openai', 'speaches', 'piper', 'cache'], required: true },
|
||||
model: { type: String, required: true },
|
||||
endpoint: { type: String, required: true },
|
||||
audioSeconds: { type: Number },
|
||||
characters: { type: Number },
|
||||
inputTokens: { type: Number },
|
||||
outputTokens: { type: Number },
|
||||
costUsd: { type: Number, required: true, default: 0 },
|
||||
createdAt: { type: Date, default: () => new Date(), index: true },
|
||||
})
|
||||
|
||||
usageEventSchema.index({ user: 1, createdAt: -1 })
|
||||
|
||||
export const UsageEvent =
|
||||
(mongoose.models.UsageEvent as mongoose.Model<UsageEventDocument> | undefined) ||
|
||||
mongoose.model<UsageEventDocument>('UsageEvent', usageEventSchema)
|
||||
Reference in New Issue
Block a user