ismdrobiul489 commited on
Commit
e224b41
Β·
1 Parent(s): 50a5f17

Complete Bar Race module with HTML UI and JS handlers

Browse files
Files changed (2) hide show
  1. modules/bar_race/__init__.py +6 -1
  2. static/index.html +130 -0
modules/bar_race/__init__.py CHANGED
@@ -8,9 +8,14 @@ from fastapi import FastAPI
8
 
9
  logger = logging.getLogger(__name__)
10
 
 
 
 
 
 
11
  _app = None
12
 
13
- def register(app: FastAPI):
14
  """Register Bar Race module routes"""
15
  global _app
16
  _app = app
 
8
 
9
  logger = logging.getLogger(__name__)
10
 
11
+ # Module metadata for auto-discovery
12
+ MODULE_NAME = "bar_race"
13
+ MODULE_PREFIX = "/api/bar-race"
14
+ MODULE_DESCRIPTION = "Bar Chart Race Video Generator"
15
+
16
  _app = None
17
 
18
+ def register(app: FastAPI, config=None):
19
  """Register Bar Race module routes"""
20
  global _app
21
  _app = app
static/index.html CHANGED
@@ -279,6 +279,9 @@
279
  <button class="tab-btn" data-tab="quiz">
280
  🎯 Quiz Reel
281
  </button>
 
 
 
282
  </div>
283
 
284
  <!-- Story Reels Tab -->
@@ -643,6 +646,69 @@
643
  </div>
644
  </div>
645
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
646
  <script>
647
  // Tab switching
648
  document.querySelectorAll('.tab-btn').forEach(btn => {
@@ -1166,6 +1232,70 @@
1166
  return id;
1167
  }
1168
  }, 100); // End of setTimeout
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1169
  </script>
1170
 
1171
  <!-- Chat Widget Button -->
 
279
  <button class="tab-btn" data-tab="quiz">
280
  🎯 Quiz Reel
281
  </button>
282
+ <button class="tab-btn" data-tab="barrace">
283
+ πŸ“Š Bar Race
284
+ </button>
285
  </div>
286
 
287
  <!-- Story Reels Tab -->
 
646
  </div>
647
  </div>
648
 
649
+ <!-- Bar Race Tab -->
650
+ <div id="barrace-tab" class="tab-content">
651
+ <div class="card">
652
+ <h2>πŸ“Š Bar Chart Race Generator</h2>
653
+ <p style="color: var(--text-secondary); margin-bottom: 1.5rem;">
654
+ Create animated bar chart race videos showing data over time
655
+ </p>
656
+
657
+ <form id="barRaceForm">
658
+ <div class="form-group">
659
+ <label>Topic *</label>
660
+ <select id="barRaceTopic" required>
661
+ <option value="gdp_nominal">GDP (Nominal) - Richest Countries</option>
662
+ <option value="population">Population - Most Populated Countries</option>
663
+ <option value="gdp_per_capita">GDP Per Capita</option>
664
+ <option value="social_media_users">Social Media Users</option>
665
+ <option value="youtube_subscribers">YouTube Subscribers</option>
666
+ <option value="military_spending">Military Expenditure</option>
667
+ <option value="olympic_medals">Olympic Gold Medals</option>
668
+ <option value="life_expectancy">Life Expectancy</option>
669
+ <option value="browser_market_share">Browser Market Share</option>
670
+ </select>
671
+ </div>
672
+
673
+ <div class="form-row" style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;">
674
+ <div class="form-group">
675
+ <label>Start Year</label>
676
+ <input type="number" id="barRaceYearStart" value="2000" min="1960" max="2024">
677
+ </div>
678
+ <div class="form-group">
679
+ <label>End Year</label>
680
+ <input type="number" id="barRaceYearEnd" value="2024" min="1960" max="2024">
681
+ </div>
682
+ </div>
683
+
684
+ <div class="form-row" style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;">
685
+ <div class="form-group">
686
+ <label>Top N (Bars)</label>
687
+ <select id="barRaceTopN">
688
+ <option value="5">5</option>
689
+ <option value="8">8</option>
690
+ <option value="10" selected>10</option>
691
+ <option value="15">15</option>
692
+ </select>
693
+ </div>
694
+ <div class="form-group">
695
+ <label>Duration (seconds)</label>
696
+ <select id="barRaceDuration">
697
+ <option value="30">30s</option>
698
+ <option value="60" selected>60s</option>
699
+ <option value="90">90s</option>
700
+ <option value="120">120s</option>
701
+ </select>
702
+ </div>
703
+ </div>
704
+
705
+ <button type="submit" class="btn btn-primary" style="width: 100%;">πŸ“Š Generate Bar Race Video</button>
706
+ </form>
707
+
708
+ <div id="barRaceStatus" class="status hidden"></div>
709
+ </div>
710
+ </div>
711
+
712
  <script>
713
  // Tab switching
714
  document.querySelectorAll('.tab-btn').forEach(btn => {
 
1232
  return id;
1233
  }
1234
  }, 100); // End of setTimeout
1235
+
1236
+ // Bar Race Form
1237
+ document.getElementById('barRaceForm').addEventListener('submit', async (e) => {
1238
+ e.preventDefault();
1239
+ const status = document.getElementById('barRaceStatus');
1240
+ status.className = 'status processing';
1241
+ status.innerHTML = '⏳ Starting bar race generation...';
1242
+
1243
+ const topic = document.getElementById('barRaceTopic').value;
1244
+ const yearStart = parseInt(document.getElementById('barRaceYearStart').value);
1245
+ const yearEnd = parseInt(document.getElementById('barRaceYearEnd').value);
1246
+ const topN = parseInt(document.getElementById('barRaceTopN').value);
1247
+ const duration = parseInt(document.getElementById('barRaceDuration').value);
1248
+
1249
+ try {
1250
+ const response = await fetch('/api/bar-race/generate', {
1251
+ method: 'POST',
1252
+ headers: { 'Content-Type': 'application/json' },
1253
+ body: JSON.stringify({
1254
+ topic: topic,
1255
+ year_start: yearStart,
1256
+ year_end: yearEnd,
1257
+ top_n: topN,
1258
+ duration_seconds: duration,
1259
+ fps: 30
1260
+ })
1261
+ });
1262
+
1263
+ const data = await response.json();
1264
+ if (!response.ok) throw new Error(data.detail || 'Failed to start');
1265
+
1266
+ status.innerHTML = `⏳ Job started: ${data.job_id}. Generating frames...`;
1267
+ pollBarRaceStatus(data.job_id);
1268
+
1269
+ } catch (err) {
1270
+ status.className = 'status error';
1271
+ status.innerHTML = '❌ ' + err.message;
1272
+ }
1273
+ });
1274
+
1275
+ async function pollBarRaceStatus(jobId) {
1276
+ const status = document.getElementById('barRaceStatus');
1277
+ const poll = async () => {
1278
+ try {
1279
+ const res = await fetch(`/api/bar-race/${jobId}/status`);
1280
+ const data = await res.json();
1281
+
1282
+ if (data.status === 'ready') {
1283
+ status.className = 'status success';
1284
+ status.innerHTML = `βœ… Video ready! <a href="${data.video_url}" target="_blank" style="color:#4ade80;">Download Video</a>`;
1285
+ } else if (data.status === 'failed') {
1286
+ status.className = 'status error';
1287
+ status.innerHTML = '❌ Failed: ' + (data.error || 'Unknown error');
1288
+ } else {
1289
+ status.innerHTML = `⏳ ${data.status}... ${data.progress}%`;
1290
+ setTimeout(poll, 2000);
1291
+ }
1292
+ } catch (err) {
1293
+ status.className = 'status error';
1294
+ status.innerHTML = '❌ Status check failed: ' + err.message;
1295
+ }
1296
+ };
1297
+ poll();
1298
+ }
1299
  </script>
1300
 
1301
  <!-- Chat Widget Button -->