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:
egg
2025-12-12 17:38:12 +08:00
parent d20751d56b
commit 65abd51d60
21 changed files with 682 additions and 662 deletions

View File

@@ -186,3 +186,34 @@ async def get_user_activity_summary(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to get user activity summary: {str(e)}"
)
@router.get("/translation-stats", summary="Get translation statistics")
async def get_translation_stats(
db: Session = Depends(get_db),
admin_user: User = Depends(get_current_admin_user)
):
"""
Get translation usage statistics for billing and monitoring.
Returns:
- total_translations: Total number of translation jobs
- total_tokens: Sum of all tokens used
- total_characters: Sum of all characters translated
- estimated_cost: Estimated cost based on token pricing
- by_language: Breakdown by target language
- recent_translations: List of recent translation activities
- last_30_days: Statistics for the last 30 days
Requires admin privileges.
"""
try:
stats = admin_service.get_translation_statistics(db)
return stats
except Exception as e:
logger.exception("Failed to get translation statistics")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"Failed to get translation statistics: {str(e)}"
)

View File

@@ -39,7 +39,8 @@ def run_translation_task(
task_id: str,
task_db_id: int,
target_lang: str,
source_lang: str = "auto"
source_lang: str = "auto",
user_id: int = None
):
"""
Background task to run document translation.
@@ -49,10 +50,12 @@ def run_translation_task(
task_db_id: Task database ID (for verification)
target_lang: Target language code
source_lang: Source language code ('auto' for detection)
user_id: User ID for logging translation statistics
"""
from app.core.database import SessionLocal
from app.services.translation_service import get_translation_service
from app.schemas.translation import TranslationJobState, TranslationProgress
from app.models.translation_log import TranslationLog
db = SessionLocal()
translation_service = get_translation_service()
@@ -132,6 +135,44 @@ def run_translation_task(
result_file_path=str(output_path) if output_path else None
))
logger.info(f"Translation completed for task {task_id}")
# Log translation statistics to database
if user_id and output_path:
try:
with open(output_path, 'r', encoding='utf-8') as f:
translation_result = json.load(f)
stats = translation_result.get('statistics', {})
total_tokens = stats.get('total_tokens', 0)
# Use actual price from Dify API if available, otherwise calculate estimated cost
actual_price = stats.get('total_price', 0.0)
if actual_price > 0:
estimated_cost = actual_price
else:
# Fallback: Calculate estimated cost based on token pricing
estimated_cost = (total_tokens / 1_000_000) * settings.translation_cost_per_million_tokens
translation_log = TranslationLog(
user_id=user_id,
task_id=task_id,
target_lang=target_lang,
source_lang=source_lang,
total_tokens=total_tokens,
input_tokens=0, # Dify doesn't provide separate input/output tokens
output_tokens=0,
total_elements=stats.get('total_elements', 0),
translated_elements=stats.get('translated_elements', 0),
total_characters=stats.get('total_characters', 0),
processing_time_seconds=stats.get('processing_time_seconds', 0.0),
provider=translation_result.get('provider', 'dify'),
estimated_cost=estimated_cost
)
db.add(translation_log)
db.commit()
logger.info(f"Logged translation stats for task {task_id}: {total_tokens} tokens, ${estimated_cost:.6f}")
except Exception as log_error:
logger.error(f"Failed to log translation stats: {log_error}")
else:
translation_service.set_job_state(task_id, TranslationJobState(
task_id=task_id,
@@ -255,7 +296,8 @@ async def start_translation(
task_id=task_id,
task_db_id=task.id,
target_lang=target_lang,
source_lang=request.source_lang
source_lang=request.source_lang,
user_id=current_user.id
)
logger.info(f"Started translation job for task {task_id}, target_lang={target_lang}")