feat: add translation billing stats and remove Export/Settings pages
- Add TranslationLog model to track translation API usage per task - Integrate Dify API actual price (total_price) into translation stats - Display translation statistics in admin dashboard with per-task costs - Remove unused Export and Settings pages to simplify frontend - Add GET /api/v2/admin/translation-stats endpoint 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@ from app.models.user import User
|
||||
from app.models.task import Task, TaskStatus
|
||||
from app.models.session import Session as UserSession
|
||||
from app.models.audit_log import AuditLog
|
||||
from app.models.translation_log import TranslationLog
|
||||
from app.core.config import settings
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -209,6 +210,87 @@ class AdminService:
|
||||
|
||||
return top_users
|
||||
|
||||
def get_translation_statistics(self, db: Session) -> dict:
|
||||
"""
|
||||
Get translation usage statistics for admin dashboard.
|
||||
|
||||
Args:
|
||||
db: Database session
|
||||
|
||||
Returns:
|
||||
Dictionary with translation stats including total tokens, costs, and breakdowns
|
||||
"""
|
||||
# Total translation count
|
||||
total_translations = db.query(TranslationLog).count()
|
||||
|
||||
# Sum of tokens
|
||||
token_stats = db.query(
|
||||
func.sum(TranslationLog.total_tokens).label("total_tokens"),
|
||||
func.sum(TranslationLog.input_tokens).label("total_input_tokens"),
|
||||
func.sum(TranslationLog.output_tokens).label("total_output_tokens"),
|
||||
func.sum(TranslationLog.total_characters).label("total_characters"),
|
||||
func.sum(TranslationLog.estimated_cost).label("total_cost")
|
||||
).first()
|
||||
|
||||
# Breakdown by target language
|
||||
by_language = db.query(
|
||||
TranslationLog.target_lang,
|
||||
func.count(TranslationLog.id).label("count"),
|
||||
func.sum(TranslationLog.total_tokens).label("tokens"),
|
||||
func.sum(TranslationLog.total_characters).label("characters")
|
||||
).group_by(TranslationLog.target_lang).all()
|
||||
|
||||
language_breakdown = [
|
||||
{
|
||||
"language": lang,
|
||||
"count": count,
|
||||
"tokens": tokens or 0,
|
||||
"characters": chars or 0
|
||||
}
|
||||
for lang, count, tokens, chars in by_language
|
||||
]
|
||||
|
||||
# Recent translations (last 20)
|
||||
recent = db.query(TranslationLog).order_by(
|
||||
TranslationLog.created_at.desc()
|
||||
).limit(20).all()
|
||||
|
||||
recent_translations = [
|
||||
{
|
||||
"id": log.id,
|
||||
"task_id": log.task_id,
|
||||
"target_lang": log.target_lang,
|
||||
"total_tokens": log.total_tokens,
|
||||
"total_characters": log.total_characters,
|
||||
"processing_time_seconds": log.processing_time_seconds,
|
||||
"estimated_cost": log.estimated_cost,
|
||||
"created_at": log.created_at.isoformat() if log.created_at else None
|
||||
}
|
||||
for log in recent
|
||||
]
|
||||
|
||||
# Stats for last 30 days
|
||||
date_30_days_ago = datetime.utcnow() - timedelta(days=30)
|
||||
recent_stats = db.query(
|
||||
func.count(TranslationLog.id).label("count"),
|
||||
func.sum(TranslationLog.total_tokens).label("tokens")
|
||||
).filter(TranslationLog.created_at >= date_30_days_ago).first()
|
||||
|
||||
return {
|
||||
"total_translations": total_translations,
|
||||
"total_tokens": token_stats.total_tokens or 0,
|
||||
"total_input_tokens": token_stats.total_input_tokens or 0,
|
||||
"total_output_tokens": token_stats.total_output_tokens or 0,
|
||||
"total_characters": token_stats.total_characters or 0,
|
||||
"estimated_cost": token_stats.total_cost or 0.0,
|
||||
"by_language": language_breakdown,
|
||||
"recent_translations": recent_translations,
|
||||
"last_30_days": {
|
||||
"count": recent_stats.count or 0,
|
||||
"tokens": recent_stats.tokens or 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Singleton instance
|
||||
admin_service = AdminService()
|
||||
|
||||
Reference in New Issue
Block a user