| <!DOCTYPE html>
|
| <html lang="en">
|
| <head>
|
| <meta charset="UTF-8">
|
| <meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| <title>Algerian Darija Transcription Service</title>
|
| <style>
|
| body {
|
| font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| max-width: 1200px;
|
| margin: 0 auto;
|
| padding: 20px;
|
| background-color: #f5f5f5;
|
| color: #333;
|
| }
|
| .container {
|
| background-color: white;
|
| border-radius: 8px;
|
| padding: 20px;
|
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
| }
|
| h1 {
|
| color: #2c3e50;
|
| border-bottom: 2px solid #3498db;
|
| padding-bottom: 10px;
|
| }
|
| .controls {
|
| display: flex;
|
| gap: 10px;
|
| margin-bottom: 20px;
|
| flex-wrap: wrap;
|
| }
|
| button {
|
| background-color: #3498db;
|
| color: white;
|
| border: none;
|
| padding: 10px 15px;
|
| border-radius: 4px;
|
| cursor: pointer;
|
| font-weight: bold;
|
| transition: background-color 0.3s;
|
| }
|
| button:hover {
|
| background-color: #2980b9;
|
| }
|
| button:disabled {
|
| background-color: #95a5a6;
|
| cursor: not-allowed;
|
| }
|
| .status {
|
| margin-top: 20px;
|
| }
|
| .status-card {
|
| background-color: #ecf0f1;
|
| border-radius: 4px;
|
| padding: 15px;
|
| margin-bottom: 20px;
|
| }
|
| .users-section {
|
| margin-top: 30px;
|
| }
|
| .user-card {
|
| background-color: #f8f9fa;
|
| border: 1px solid #dee2e6;
|
| border-radius: 4px;
|
| padding: 10px;
|
| margin-bottom: 10px;
|
| display: flex;
|
| justify-content: space-between;
|
| align-items: center;
|
| }
|
| .user-stats {
|
| display: flex;
|
| gap: 15px;
|
| }
|
| .loader {
|
| border: 3px solid #f3f3f3;
|
| border-top: 3px solid #3498db;
|
| border-radius: 50%;
|
| width: 20px;
|
| height: 20px;
|
| animation: spin 2s linear infinite;
|
| display: inline-block;
|
| margin-left: 10px;
|
| vertical-align: middle;
|
| }
|
| @keyframes spin {
|
| 0% { transform: rotate(0deg); }
|
| 100% { transform: rotate(360deg); }
|
| }
|
| .hidden {
|
| display: none;
|
| }
|
| .alert {
|
| padding: 10px;
|
| border-radius: 4px;
|
| margin-bottom: 10px;
|
| }
|
| .alert-success {
|
| background-color: #d4edda;
|
| color: #155724;
|
| border: 1px solid #c3e6cb;
|
| }
|
| .alert-error {
|
| background-color: #f8d7da;
|
| color: #721c24;
|
| border: 1px solid #f5c6cb;
|
| }
|
| .warning-banner {
|
| background-color: #fff3cd;
|
| color: #856404;
|
| border: 1px solid #ffeeba;
|
| padding: 10px;
|
| border-radius: 4px;
|
| margin-bottom: 20px;
|
| text-align: center;
|
| }
|
| .uptime {
|
| font-size: 0.9em;
|
| color: #666;
|
| margin-left: 10px;
|
| }
|
| </style>
|
| </head>
|
| <body>
|
| <div class="container">
|
| <h1>Algerian Darija Transcription Service</h1>
|
|
|
| <div class="warning-banner">
|
| <strong>Note:</strong> This service is running on Hugging Face Spaces with ephemeral storage.
|
| Download processed data regularly to avoid data loss.
|
| </div>
|
|
|
| <div id="alert" class="hidden"></div>
|
|
|
| <div class="controls">
|
| <button id="startBtn">Start Bot</button>
|
| <button id="stopBtn">Stop Bot</button>
|
| <button id="refreshBtn">Refresh Status</button>
|
| <button id="downloadAllBtn">Download All Processed Data</button>
|
| </div>
|
|
|
| <div class="status">
|
| <h2>Status <span id="loader" class="loader hidden"></span></h2>
|
| <div class="status-card">
|
| <p>
|
| <strong>Bot Status:</strong> <span id="botStatus">Unknown</span>
|
| <span id="uptime" class="uptime"></span>
|
| </p>
|
| <p><strong>Pending Voice Files:</strong> <span id="pendingVoice">-</span></p>
|
| <p><strong>Pending Text Files:</strong> <span id="pendingText">-</span></p>
|
| <p><strong>Matching Pending Pairs:</strong> <span id="matchingPairs">-</span></p>
|
| <p><strong>Processed Voice Files:</strong> <span id="processedVoice">-</span></p>
|
| <p><strong>Processed Text Files:</strong> <span id="processedText">-</span></p>
|
| </div>
|
| </div>
|
|
|
| <div class="users-section">
|
| <h2>User Data</h2>
|
| <div id="users-container">
|
| <p>Loading user data...</p>
|
| </div>
|
| </div>
|
| </div>
|
|
|
| <script>
|
| const startBtn = document.getElementById('startBtn');
|
| const stopBtn = document.getElementById('stopBtn');
|
| const refreshBtn = document.getElementById('refreshBtn');
|
| const downloadAllBtn = document.getElementById('downloadAllBtn');
|
| const loader = document.getElementById('loader');
|
| const alert = document.getElementById('alert');
|
|
|
|
|
| updateStatus();
|
|
|
|
|
| setInterval(updateStatus, 30000);
|
|
|
| startBtn.addEventListener('click', async () => {
|
| try {
|
| showLoader();
|
| const response = await fetch('/start-bot', {
|
| method: 'POST'
|
| });
|
| const data = await response.json();
|
|
|
| if (data.status === 'success') {
|
| showAlert('success', data.message);
|
| } else {
|
| showAlert('error', data.message);
|
| }
|
|
|
| updateStatus();
|
| } catch (error) {
|
| showAlert('error', 'Failed to start bot: ' + error.message);
|
| hideLoader();
|
| }
|
| });
|
|
|
| stopBtn.addEventListener('click', async () => {
|
| try {
|
| showLoader();
|
| const response = await fetch('/stop-bot', {
|
| method: 'POST'
|
| });
|
| const data = await response.json();
|
|
|
| if (data.status === 'success') {
|
| showAlert('success', data.message);
|
| } else {
|
| showAlert('error', data.message);
|
| }
|
|
|
| updateStatus();
|
| } catch (error) {
|
| showAlert('error', 'Failed to stop bot: ' + error.message);
|
| hideLoader();
|
| }
|
| });
|
|
|
| refreshBtn.addEventListener('click', () => {
|
| updateStatus();
|
| });
|
|
|
| downloadAllBtn.addEventListener('click', () => {
|
| window.location.href = '/download-all';
|
| });
|
|
|
| function formatUptime(seconds) {
|
| if (!seconds) return '';
|
|
|
| const hours = Math.floor(seconds / 3600);
|
| const minutes = Math.floor((seconds % 3600) / 60);
|
| const secs = Math.floor(seconds % 60);
|
|
|
| return `(Running for ${hours}h ${minutes}m ${secs}s)`;
|
| }
|
|
|
| async function updateStatus() {
|
| try {
|
| showLoader();
|
|
|
|
|
| const statusResponse = await fetch('/status');
|
| const statusData = await statusResponse.json();
|
|
|
|
|
| document.getElementById('botStatus').textContent = statusData.bot_running ? 'Running' : 'Stopped';
|
| document.getElementById('uptime').textContent = statusData.bot_running ? formatUptime(statusData.uptime_seconds) : '';
|
| document.getElementById('pendingVoice').textContent = statusData.pending_voice_count;
|
| document.getElementById('pendingText').textContent = statusData.pending_text_count;
|
| document.getElementById('matchingPairs').textContent = statusData.matching_pairs;
|
| document.getElementById('processedVoice').textContent = statusData.processed_voice_count;
|
| document.getElementById('processedText').textContent = statusData.processed_text_count;
|
|
|
|
|
| startBtn.disabled = statusData.bot_running;
|
| stopBtn.disabled = !statusData.bot_running;
|
|
|
|
|
| const usersContainer = document.getElementById('users-container');
|
|
|
| if (Object.keys(statusData.user_stats).length > 0) {
|
| let usersHtml = '';
|
|
|
| for (const [userId, stats] of Object.entries(statusData.user_stats)) {
|
| usersHtml += `
|
| <div class="user-card">
|
| <div>
|
| <strong>${userId}</strong>
|
| <div class="user-stats">
|
| <span>Voice Files: ${stats.voice}</span>
|
| <span>Text Files: ${stats.text}</span>
|
| </div>
|
| </div>
|
| <a href="/download/${userId}" target="_blank">
|
| <button>Download Data</button>
|
| </a>
|
| </div>
|
| `;
|
| }
|
|
|
| usersContainer.innerHTML = usersHtml;
|
| } else {
|
| usersContainer.innerHTML = '<p>No processed data available yet.</p>';
|
| }
|
|
|
| hideLoader();
|
| } catch (error) {
|
| console.error('Error updating status:', error);
|
| showAlert('error', 'Failed to update status: ' + error.message);
|
| hideLoader();
|
| }
|
| }
|
|
|
| function showLoader() {
|
| loader.classList.remove('hidden');
|
| }
|
|
|
| function hideLoader() {
|
| loader.classList.add('hidden');
|
| }
|
|
|
| function showAlert(type, message) {
|
| alert.className = `alert alert-${type}`;
|
| alert.textContent = message;
|
| alert.classList.remove('hidden');
|
|
|
|
|
| setTimeout(() => {
|
| alert.classList.add('hidden');
|
| }, 5000);
|
| }
|
| </script>
|
| </body>
|
| </html> |