video-accessibility/frontend/src/utils/jobStatusMessages.ts
Vadym Samoilenko c1948ea198 feat(ux): T-2/PR-7/PR-8 — status color helper, queue stats widget, upload-final-VTT override
T-2: Extract getJobStatusColor() into utils/jobStatusMessages.ts; StatusBadge now uses the
     shared helper (single source of truth for badge colors).

PR-7: GET /admin/production/queue-stats — returns Celery queue depths via Redis LLEN.
      Production dashboard shows a live panel (10s refresh) with per-queue task counts.

PR-8: POST /admin/production/jobs/{id}/upload-final-vtt — Production/Admin can upload a
      hand-crafted VTT to bypass AI, writing to GCS and advancing the job to PENDING_QC.
      Upload modal added to FailuresList with language + type (captions/ad) selectors.

docker-compose.optical-dev.yml: enable USE_CELERY_FALLBACK=true, set worker replicas=1
      for all pipeline workers (ffmpeg/tts/whisper) with WORKER_CONCURRENCY=2 so the full
      pipeline runs on the 2-CPU optical-dev server until Cloud Run VPC Connector is ready.

Fix: remove unused effectiveMs variable in TimelinePreview (TS6133).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 11:12:36 +01:00

190 lines
No EOL
5.7 KiB
TypeScript

/**
* Utility functions for generating user-friendly job status messages and toast notifications
*/
export interface StatusMessageConfig {
message: string;
type: 'success' | 'info' | 'warning' | 'error';
showToast: boolean;
}
/**
* Get user-friendly message and toast configuration for job status updates
*/
export function getStatusMessageConfig(
status: string,
jobTitle?: string,
customMessage?: string
): StatusMessageConfig {
const title = jobTitle ? `"${jobTitle}"` : 'Job';
const fallbackMessage = customMessage || '';
switch (status) {
case 'created':
return {
message: `${title} has been created and queued for processing`,
type: 'info',
showToast: true
};
case 'ingesting':
return {
message: `${title} is being ingested and prepared for AI processing`,
type: 'info',
showToast: true
};
case 'ai_processing':
return {
message: `${title} is being processed by AI to generate accessibility features`,
type: 'info',
showToast: true
};
case 'pending_qc':
return {
message: customMessage || `${title} is ready for quality control review`,
type: customMessage ? 'success' : 'info',
showToast: true
};
case 'approved_english':
case 'approved_source':
return {
message: `${title} source content has been approved - starting translation`,
type: 'success',
showToast: true
};
case 'rejected':
return {
message: `${title} has been rejected and requires revision`,
type: 'warning',
showToast: true
};
case 'translating':
return {
message: `${title} is being translated and transcreated into requested languages`,
type: 'info',
showToast: true
};
case 'tts_generating':
return {
message: `${title} is generating audio descriptions with text-to-speech`,
type: 'info',
showToast: true
};
case 'rendering_video':
return {
message: `${title} is rendering accessible video with embedded audio descriptions`,
type: 'info',
showToast: true
};
case 'rendering_qc':
return {
message: customMessage || `${title} is re-rendering accessible video with your changes`,
type: 'info',
showToast: true
};
case 'pending_final_review':
return {
message: `${title} is ready for final review before completion`,
type: 'info',
showToast: true
};
case 'qc_feedback':
return {
message: `${title} final review has been rejected - requires changes`,
type: 'warning',
showToast: true
};
case 'completed':
return {
message: `${title} has been completed successfully! 🎉 All files are ready for download`,
type: 'success',
showToast: true
};
default:
return {
message: fallbackMessage || `${title} status updated to ${status}`,
type: 'info',
showToast: !!fallbackMessage
};
}
}
/**
* Get a shorter status message for progress updates
*/
export function getProgressMessage(status: string, progress?: number): string {
const progressText = progress !== undefined ? ` (${progress}%)` : '';
switch (status) {
case 'ingesting':
return `Ingesting video${progressText}`;
case 'ai_processing':
return `AI processing${progressText}`;
case 'translating':
return `Translating content${progressText}`;
case 'tts_generating':
return `Generating audio${progressText}`;
case 'rendering_video':
return `Rendering video${progressText}`;
default:
return status.replace(/_/g, ' ');
}
}
/** Single source of truth for human-readable job status labels. */
export const JOB_STATUS_LABELS: Record<string, string> = {
created: 'Created',
ingesting: 'Ingesting',
ai_processing: 'AI Processing',
pending_qc: 'Pending QC',
approved_english: 'Approved (EN)',
approved_source: 'Approved for Translation',
rejected: 'Rejected',
qc_feedback: 'QC Feedback',
translating: 'Translating',
tts_generating: 'Generating Audio',
tts_failed: 'TTS Failed',
rendering_video: 'Rendering Video',
render_failed: 'Render Failed',
pending_final_review: 'Pending Final Review',
completed: 'Completed',
};
export const getJobStatusLabel = (status: string): string =>
JOB_STATUS_LABELS[status] ?? status.replace(/_/g, ' ');
/** Tailwind classes for job status badges (bg + text). */
export function getJobStatusColor(status: string): string {
switch (status) {
case 'created': return 'bg-gray-100 text-gray-800';
case 'ingesting': return 'bg-blue-100 text-blue-800';
case 'ai_processing': return 'bg-purple-100 text-purple-800';
case 'pending_qc': return 'bg-yellow-100 text-yellow-800';
case 'approved_english':
case 'approved_source':
case 'completed': return 'bg-green-100 text-green-800';
case 'rejected':
case 'tts_failed':
case 'render_failed':
case 'processing_failed': return 'bg-red-100 text-red-800';
case 'qc_feedback': return 'bg-orange-100 text-orange-800';
case 'translating': return 'bg-blue-100 text-blue-800';
case 'tts_generating': return 'bg-indigo-100 text-indigo-800';
case 'rendering_video': return 'bg-violet-100 text-violet-800';
case 'rendering_qc': return 'bg-violet-100 text-violet-800';
case 'pending_final_review':return 'bg-orange-100 text-orange-800';
default: return 'bg-gray-100 text-gray-800';
}
}