Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AI Campaign Monitor</title> | |
| <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"> | |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"> | |
| <style> | |
| .stage-active { background: linear-gradient(45deg, #007bff, #0056b3); color: white; } | |
| .stage-completed { background: linear-gradient(45deg, #28a745, #1e7e34); color: white; } | |
| .live-update { animation: slideIn 0.5s ease-out; } | |
| @keyframes slideIn { | |
| from { opacity: 0; transform: translateY(-20px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-light"> | |
| <div class="container-fluid py-4"> | |
| <!-- Progress Header --> | |
| <div class="row mb-4"> | |
| <div class="col-12"> | |
| <div class="card shadow-sm"> | |
| <div class="card-body"> | |
| <div class="row align-items-center"> | |
| <div class="col-md-8"> | |
| <h2 id="campaignTitle">AI Campaign in Progress</h2> | |
| <p class="text-muted" id="campaignSubtitle">Processing...</p> | |
| </div> | |
| <div class="col-md-4 text-end"> | |
| <div class="progress" style="height: 20px;"> | |
| <div class="progress-bar" id="progressBar" style="width: 0%">0%</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Stage Indicators --> | |
| <div class="row mb-4"> | |
| <div class="col-12"> | |
| <div class="card shadow-sm"> | |
| <div class="card-body"> | |
| <h5>π Campaign Stages</h5> | |
| <div class="row" id="stageIndicators"> | |
| <!-- Will be populated by JavaScript --> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Real-time Metrics --> | |
| <div class="row mb-4"> | |
| <div class="col-md-3"> | |
| <div class="card text-center"> | |
| <div class="card-body"> | |
| <i class="fas fa-users fa-2x text-primary mb-2"></i> | |
| <h4 id="influencerCount">0</h4> | |
| <small>Influencers Found</small> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="col-md-3"> | |
| <div class="card text-center"> | |
| <div class="card-body"> | |
| <i class="fas fa-phone fa-2x text-success mb-2"></i> | |
| <h4 id="callsCompleted">0</h4> | |
| <small>Calls Completed</small> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="col-md-3"> | |
| <div class="card text-center"> | |
| <div class="card-body"> | |
| <i class="fas fa-handshake fa-2x text-warning mb-2"></i> | |
| <h4 id="successfulDeals">0</h4> | |
| <small>Successful Deals</small> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="col-md-3"> | |
| <div class="card text-center"> | |
| <div class="card-body"> | |
| <i class="fas fa-dollar-sign fa-2x text-info mb-2"></i> | |
| <h4 id="totalCost">$0</h4> | |
| <small>Total Cost</small> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Live Activity Feed --> | |
| <div class="row"> | |
| <div class="col-md-8"> | |
| <div class="card"> | |
| <div class="card-header"> | |
| <h5>π΄ Live Activity Feed</h5> | |
| </div> | |
| <div class="card-body" style="height: 400px; overflow-y: auto;"> | |
| <div id="liveUpdates"> | |
| <div class="text-center py-4"> | |
| <div class="spinner-border text-primary"></div> | |
| <div class="mt-2">Connecting...</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="col-md-4"> | |
| <div class="card"> | |
| <div class="card-header"> | |
| <h5>β±οΈ Timing Info</h5> | |
| </div> | |
| <div class="card-body"> | |
| <div class="mb-3"> | |
| <small class="text-muted">Started At</small> | |
| <div id="startedAt">-</div> | |
| </div> | |
| <div class="mb-3"> | |
| <small class="text-muted">Duration</small> | |
| <div id="duration">-</div> | |
| </div> | |
| <div class="mb-3"> | |
| <small class="text-muted">Estimated Remaining</small> | |
| <div id="estimatedRemaining">-</div> | |
| </div> | |
| <div> | |
| <small class="text-muted">Current Activity</small> | |
| <div id="currentInfluencer">None</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <script> | |
| // Get the task ID - replace this with your actual task ID | |
| const TASK_ID = 'your-task-id-here'; // β οΈ Replace with actual task ID | |
| class CampaignMonitor { | |
| constructor(taskId) { | |
| this.taskId = taskId; | |
| this.updateInterval = 5000; // 5 seconds | |
| this.isMonitoring = false; | |
| this.pollingId = null; | |
| this.stages = [ | |
| { key: 'webhook_received', name: 'Starting', icon: 'fas fa-play' }, | |
| { key: 'discovery', name: 'Discovery', icon: 'fas fa-search' }, | |
| { key: 'negotiations', name: 'Negotiations', icon: 'fas fa-phone' }, | |
| { key: 'contracts', name: 'Contracts', icon: 'fas fa-file-contract' }, | |
| { key: 'completed', name: 'Complete', icon: 'fas fa-check' } | |
| ]; | |
| this.initializeUI(); | |
| } | |
| initializeUI() { | |
| this.renderStageIndicators(); | |
| } | |
| renderStageIndicators() { | |
| const container = document.getElementById('stageIndicators'); | |
| container.innerHTML = this.stages.map(stage => ` | |
| <div class="col"> | |
| <div class="text-center p-3 rounded border" id="stage-${stage.key}"> | |
| <i class="${stage.icon} fa-lg mb-2"></i> | |
| <div class="small">${stage.name}</div> | |
| </div> | |
| </div> | |
| `).join(''); | |
| } | |
| async startMonitoring() { | |
| console.log(`π Starting monitoring for: ${this.taskId}`); | |
| this.isMonitoring = true; | |
| // Initial fetch | |
| await this.fetchCampaignStatus(); | |
| // Start polling | |
| this.pollingId = setInterval(async () => { | |
| if (this.isMonitoring) { | |
| await this.fetchCampaignStatus(); | |
| } | |
| }, this.updateInterval); | |
| } | |
| async fetchCampaignStatus() { | |
| try { | |
| const response = await fetch(`/api/monitor/campaign/${this.taskId}`); | |
| if (!response.ok) { | |
| throw new Error(`HTTP ${response.status}: ${response.statusText}`); | |
| } | |
| const data = await response.json(); | |
| this.updateUI(data); | |
| if (data.is_complete) { | |
| this.onCampaignComplete(data); | |
| } | |
| } catch (error) { | |
| console.error('β Monitoring error:', error); | |
| this.handleError(error); | |
| } | |
| } | |
| updateUI(data) { | |
| // Update header | |
| document.getElementById('campaignTitle').textContent = | |
| `${data.campaign_info.brand_name} - ${data.campaign_info.product_name}`; | |
| document.getElementById('campaignSubtitle').textContent = | |
| this.getStageDescription(data.current_stage, data.current_influencer); | |
| // Update progress bar | |
| const progressBar = document.getElementById('progressBar'); | |
| progressBar.style.width = `${data.progress_percentage}%`; | |
| progressBar.textContent = `${Math.round(data.progress_percentage)}%`; | |
| // Update stage indicators | |
| this.updateStageIndicators(data.current_stage); | |
| // Update metrics | |
| const details = data.progress_details; | |
| document.getElementById('influencerCount').textContent = details.discovered_influencers; | |
| document.getElementById('callsCompleted').textContent = details.completed_negotiations; | |
| document.getElementById('successfulDeals').textContent = details.successful_negotiations; | |
| document.getElementById('totalCost').textContent = `$${details.total_cost.toLocaleString()}`; | |
| // Update live feed | |
| this.updateLiveFeed(data.live_updates); | |
| // Update timing | |
| const timing = data.timing; | |
| document.getElementById('startedAt').textContent = new Date(timing.started_at).toLocaleString(); | |
| document.getElementById('duration').textContent = `${Math.round(timing.duration_so_far)} min`; | |
| document.getElementById('estimatedRemaining').textContent = timing.estimated_remaining; | |
| document.getElementById('currentInfluencer').textContent = data.current_influencer || 'None'; | |
| } | |
| updateStageIndicators(currentStage) { | |
| this.stages.forEach(stage => { | |
| const element = document.getElementById(`stage-${stage.key}`); | |
| element.className = 'text-center p-3 rounded border'; | |
| if (stage.key === currentStage) { | |
| element.classList.add('stage-active'); | |
| } else if (this.isStageCompleted(stage.key, currentStage)) { | |
| element.classList.add('stage-completed'); | |
| } | |
| }); | |
| } | |
| updateLiveFeed(updates) { | |
| const container = document.getElementById('liveUpdates'); | |
| // Clear loading state | |
| if (container.innerHTML.includes('Connecting')) { | |
| container.innerHTML = ''; | |
| } | |
| // Add new updates | |
| updates.forEach(update => { | |
| if (!this.isUpdateDisplayed(update)) { | |
| const updateElement = this.createUpdateElement(update); | |
| container.insertBefore(updateElement, container.firstChild); | |
| } | |
| }); | |
| // Limit to last 10 updates | |
| while (container.children.length > 10) { | |
| container.removeChild(container.lastChild); | |
| } | |
| } | |
| createUpdateElement(update) { | |
| const div = document.createElement('div'); | |
| div.className = 'border-bottom pb-2 mb-2 live-update'; | |
| div.innerHTML = ` | |
| <div class="d-flex justify-content-between"> | |
| <div>${this.formatUpdate(update)}</div> | |
| <small class="text-muted">${new Date().toLocaleTimeString()}</small> | |
| </div> | |
| `; | |
| return div; | |
| } | |
| formatUpdate(update) { | |
| if (update.includes('β ')) return `<span class="text-success">${update}</span>`; | |
| if (update.includes('β')) return `<span class="text-danger">${update}</span>`; | |
| if (update.includes('π')) return `<span class="text-primary">${update}</span>`; | |
| if (update.includes('π')) return `<span class="text-info">${update}</span>`; | |
| return update; | |
| } | |
| getStageDescription(stage, currentInfluencer) { | |
| const descriptions = { | |
| 'webhook_received': 'Initializing campaign workflow...', | |
| 'discovery': 'Analyzing and discovering potential influencers...', | |
| 'negotiations': currentInfluencer ? | |
| `Currently negotiating with ${currentInfluencer}...` : | |
| 'Conducting influencer negotiations...', | |
| 'contracts': 'Generating contracts and finalizing deals...', | |
| 'completed': 'Campaign completed successfully!' | |
| }; | |
| return descriptions[stage] || 'Processing...'; | |
| } | |
| isStageCompleted(stage, currentStage) { | |
| const stageOrder = this.stages.map(s => s.key); | |
| const currentIndex = stageOrder.indexOf(currentStage); | |
| const stageIndex = stageOrder.indexOf(stage); | |
| return stageIndex < currentIndex; | |
| } | |
| isUpdateDisplayed(update) { | |
| const container = document.getElementById('liveUpdates'); | |
| return Array.from(container.children).some(child => | |
| child.textContent.includes(update.substring(0, 50)) | |
| ); | |
| } | |
| onCampaignComplete(data) { | |
| this.stopMonitoring(); | |
| // Show success message | |
| const container = document.getElementById('liveUpdates'); | |
| const completionMsg = document.createElement('div'); | |
| completionMsg.className = 'alert alert-success'; | |
| completionMsg.innerHTML = ` | |
| <h5>π Campaign Complete!</h5> | |
| <p>Successfully completed ${data.progress_details.successful_negotiations} deals | |
| for $${data.progress_details.total_cost.toLocaleString()}</p> | |
| `; | |
| container.insertBefore(completionMsg, container.firstChild); | |
| } | |
| handleError(error) { | |
| const container = document.getElementById('liveUpdates'); | |
| const errorMsg = document.createElement('div'); | |
| errorMsg.className = 'alert alert-warning'; | |
| errorMsg.innerHTML = `β Connection error: ${error.message}. Retrying...`; | |
| container.insertBefore(errorMsg, container.firstChild); | |
| } | |
| stopMonitoring() { | |
| this.isMonitoring = false; | |
| if (this.pollingId) { | |
| clearInterval(this.pollingId); | |
| this.pollingId = null; | |
| } | |
| console.log('βΉοΈ Campaign monitoring stopped'); | |
| } | |
| } | |
| // Initialize when page loads | |
| document.addEventListener('DOMContentLoaded', function() { | |
| // Get task ID from URL parameters or use the constant | |
| const urlParams = new URLSearchParams(window.location.search); | |
| const taskId = urlParams.get('task_id') || TASK_ID; | |
| if (taskId && taskId !== 'your-task-id-here') { | |
| const monitor = new CampaignMonitor(taskId); | |
| monitor.startMonitoring(); | |
| // Stop monitoring when page closes | |
| window.addEventListener('beforeunload', () => { | |
| monitor.stopMonitoring(); | |
| }); | |
| } else { | |
| document.getElementById('liveUpdates').innerHTML = ` | |
| <div class="alert alert-info"> | |
| <h5>βΉοΈ Setup Required</h5> | |
| <p>Please provide a task_id parameter in the URL or update the TASK_ID constant in the JavaScript.</p> | |
| <p><strong>Example:</strong> monitor.html?task_id=your-actual-task-id</p> | |
| </div> | |
| `; | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> |