seawolf2357 commited on
Commit
18a8ea2
·
verified ·
1 Parent(s): b356b82

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +55 -69
app.py CHANGED
@@ -81,7 +81,7 @@ UI_LANG = {
81
  }
82
 
83
  # ============================================
84
- # 🎨 CSS - Hide HF Elements + Comic Theme
85
  # ============================================
86
  css = """
87
  @import url('https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@400;700&display=swap');
@@ -167,7 +167,7 @@ label { color: #1F2937 !important; font-family: 'Comic Neue', cursive !important
167
  ::-webkit-scrollbar-thumb { background: #3B82F6; border: 2px solid #1F2937; }
168
  ::selection { background: #FACC15; color: #1F2937; }
169
 
170
- /* LLM Result box - 녹색 글자 on 검정 배경 */
171
  .llm-result textarea {
172
  background: #1F2937 !important;
173
  color: #10B981 !important;
@@ -360,12 +360,12 @@ def use_trending_keyword(kw):
360
  return kw if kw else ""
361
 
362
  # ============================================
363
- # 🔍 Main Search Function with WORKING SORT
364
  # ============================================
365
  def search_videos(keyword, country, language, sort_by, date_filter, max_results, ui_lang):
366
  L = UI_LANG.get(ui_lang, UI_LANG["en"])
367
  if not keyword or not keyword.strip():
368
- return f"⚠️ {L['no_keyword']}", ""
369
 
370
  max_results = int(max_results)
371
  all_items, next_page = [], None
@@ -388,7 +388,7 @@ def search_videos(keyword, country, language, sort_by, date_filter, max_results,
388
  try:
389
  resp = youtube.search().list(**params).execute()
390
  except Exception as e:
391
- return f"API Error: {e}", ""
392
  items = resp.get("items", [])
393
  if not items: break
394
  all_items.extend(items)
@@ -396,7 +396,7 @@ def search_videos(keyword, country, language, sort_by, date_filter, max_results,
396
  if not next_page: break
397
 
398
  if not all_items:
399
- return f"{L['no_results']}", ""
400
 
401
  video_ids = [item["id"]["videoId"] for item in all_items]
402
  channel_ids = list(set([item["snippet"]["channelId"] for item in all_items]))
@@ -432,7 +432,6 @@ def search_videos(keyword, country, language, sort_by, date_filter, max_results,
432
  ai_ratings = get_ai_pick_rating(videos_data)
433
  save_to_db(videos_data, channel_subs_raw, keyword, country, language, sort_by)
434
 
435
- # 고유 ID
436
  uid = str(uuid.uuid4()).replace("-", "")[:8]
437
 
438
  html = f'''
@@ -440,11 +439,11 @@ def search_videos(keyword, country, language, sort_by, date_filter, max_results,
440
  #tbl_{uid} {{ width:100%; border-collapse:collapse; font-family:'Comic Neue',cursive; }}
441
  #tbl_{uid} th {{
442
  background:#EF4444; color:#fff; padding:12px 6px; border:2px solid #1F2937;
443
- font-family:'Bangers',cursive; cursor:pointer; user-select:none; position:relative;
444
  }}
445
  #tbl_{uid} th:hover {{ background:#DC2626; }}
446
- #tbl_{uid} th.asc::after {{ content:" ▲"; color:#FACC15; }}
447
- #tbl_{uid} th.desc::after {{ content:" ▼"; color:#FACC15; }}
448
  #tbl_{uid} td {{ padding:8px 6px; border-bottom:2px solid #1F2937; background:#FFF; vertical-align:middle; }}
449
  #tbl_{uid} tr:hover td {{ background:#FEF9C3; }}
450
  #tbl_{uid} img {{ border-radius:4px; border:2px solid #1F2937; }}
@@ -460,28 +459,28 @@ def search_videos(keyword, country, language, sort_by, date_filter, max_results,
460
  🎬 {L["total"]} <b>{len(videos_data)}</b> {L["results"]} | 🔍 "{keyword}" | 🌍 {country}
461
  <br><span style="font-size:0.85rem">🤖 AI Pick: ♥ ⭐ ⭐⭐ ⭐⭐⭐ | 💡 {"헤더 클릭 = 정렬" if ui_lang=="ko" else "Click header to sort"}</span>
462
  <div style="margin-top:10px">
463
- <button class="sortbtn_{uid}" onclick="doSort_{uid}(4)">{L["subs"]}</button>
464
- <button class="sortbtn_{uid}" onclick="doSort_{uid}(5)">{L["views"]}</button>
465
- <button class="sortbtn_{uid}" onclick="doSort_{uid}(6)">{L["likes"]}</button>
466
- <button class="sortbtn_{uid}" onclick="doSort_{uid}(7)">{L["comments"]}</button>
467
- <button class="sortbtn_{uid}" onclick="doSort_{uid}(8)">AI</button>
468
- <button class="sortbtn_{uid}" onclick="doSort_{uid}(9)">{L["date"]}</button>
469
  </div>
470
  </div>
471
 
472
  <div style="max-height:700px; overflow-y:auto; border:3px solid #1F2937; border-radius:8px;">
473
  <table id="tbl_{uid}">
474
  <thead><tr>
475
- <th data-col="0" data-type="n" style="width:45px">{L["rank"]}</th>
476
  <th style="width:120px">{L["thumb"]}</th>
477
- <th data-col="2" data-type="s">{L["title_col"]}</th>
478
- <th data-col="3" data-type="s" style="width:100px">{L["channel"]}</th>
479
- <th data-col="4" data-type="n" style="width:70px">{L["subs"]}</th>
480
- <th data-col="5" data-type="n" style="width:75px">{L["views"]}</th>
481
- <th data-col="6" data-type="n" style="width:60px">{L["likes"]}</th>
482
- <th data-col="7" data-type="n" style="width:55px">{L["comments"]}</th>
483
- <th data-col="8" data-type="n" style="width:65px">{L["ai_pick_col"]}</th>
484
- <th data-col="9" data-type="s" style="width:90px">{L["date"]}</th>
485
  </tr></thead>
486
  <tbody>
487
  '''
@@ -507,7 +506,6 @@ def search_videos(keyword, country, language, sort_by, date_filter, max_results,
507
  <td data-v="{v['published_at'][:10]}">{v['published_at'][:10]}</td>
508
  </tr>'''
509
 
510
- # JavaScript 정렬 - 즉시 실행
511
  html += f'''
512
  </tbody>
513
  </table>
@@ -515,22 +513,22 @@ def search_videos(keyword, country, language, sort_by, date_filter, max_results,
515
 
516
  <script>
517
  (function() {{
518
- var tbl = document.getElementById('tbl_{uid}');
519
- if (!tbl) return;
520
 
521
- var sortState = {{}};
522
-
523
- function sortTable(colIdx, type) {{
 
524
  var tbody = tbl.querySelector('tbody');
525
  var rows = Array.from(tbody.querySelectorAll('tr'));
526
  var headers = tbl.querySelectorAll('th');
527
 
528
- var asc = sortState[colIdx] !== 'asc';
529
- sortState = {{}};
530
- sortState[colIdx] = asc ? 'asc' : 'desc';
531
 
532
- headers.forEach(function(h) {{ h.classList.remove('asc', 'desc'); }});
533
- if (headers[colIdx]) headers[colIdx].classList.add(asc ? 'asc' : 'desc');
534
 
535
  rows.sort(function(a, b) {{
536
  var aCell = a.cells[colIdx];
@@ -550,21 +548,6 @@ def search_videos(keyword, country, language, sort_by, date_filter, max_results,
550
  }});
551
 
552
  rows.forEach(function(row) {{ tbody.appendChild(row); }});
553
- }}
554
-
555
- // 헤더 클릭 이벤트
556
- tbl.querySelectorAll('th[data-col]').forEach(function(th) {{
557
- th.addEventListener('click', function() {{
558
- var col = parseInt(this.getAttribute('data-col'));
559
- var type = this.getAttribute('data-type');
560
- sortTable(col, type);
561
- }});
562
- }});
563
-
564
- // 전역 정렬 함수
565
- window.doSort_{uid} = function(col) {{
566
- var type = (col >= 4 && col <= 8) ? 'n' : 's';
567
- sortTable(col, type);
568
  }};
569
  }})();
570
  </script>
@@ -574,7 +557,7 @@ def search_videos(keyword, country, language, sort_by, date_filter, max_results,
574
  return html, f"📊 DB: Videos {stats['videos']} | Records {stats['stats']} | Channels {stats['channels']} | Searches {stats['searches']}"
575
 
576
  # ============================================
577
- # 🔥 Trending Alerts (20%+ Surge)
578
  # ============================================
579
  def show_trending_alerts(ui_lang):
580
  is_ko = ui_lang == "ko"
@@ -629,7 +612,7 @@ def show_trending_alerts(ui_lang):
629
  return html + '</div>'
630
 
631
  # ============================================
632
- # 📈 Top Growing (48h Growth Rate)
633
  # ============================================
634
  def show_top_growing(ui_lang):
635
  is_ko = ui_lang == "ko"
@@ -694,7 +677,7 @@ def show_top_growing(ui_lang):
694
  return html + '</div>'
695
 
696
  # ============================================
697
- # ⭐ AI Pick (Fixed colors)
698
  # ============================================
699
  def show_ai_picks(ui_lang):
700
  is_ko = ui_lang == "ko"
@@ -710,7 +693,6 @@ def show_ai_picks(ui_lang):
710
  top_picks = [(i, v, ratings.get(i, 0)) for i, v in enumerate(videos) if ratings.get(i, 0) >= 3]
711
  top_picks.sort(key=lambda x: (-x[2], -x[1]['views']))
712
 
713
- # AI 분석 - 색상 수정됨
714
  analysis_html = ""
715
  if groq_client and top_picks:
716
  info = "\n".join([f"- {v['title'][:50]} (Views:{format_count(v['views'])})" for _, v, _ in top_picks[:5]])
@@ -726,7 +708,7 @@ def show_ai_picks(ui_lang):
726
  <div style="background:linear-gradient(135deg,#FACC15,#F59E0B);padding:20px;border:3px solid #1F2937;border-radius:8px;margin-bottom:15px">
727
  <h2 style="font-family:'Bangers',cursive;color:#1F2937;margin:0;text-shadow:2px 2px 0 #FFF;font-size:2rem">⭐ {"AI 추천 - TOP 영상" if is_ko else "AI PICK - TOP RECOMMENDATIONS"}</h2>
728
  <p style="color:#1F2937;margin:10px 0 0;font-family:'Comic Neue',cursive;font-weight:700;font-size:15px">
729
- {"⭐⭐ 이상 등급 영상" if is_ko else "⭐⭐+ rated videos"}: <b>{len(top_picks)}</b>{"개" if is_ko else " videos"} | {"마지막 업데이트" if is_ko else "Last updated"}: {ai_pick_storage.get("timestamp", "")[:16].replace("T"," ")}
730
  </p>
731
  </div>
732
  {analysis_html}
@@ -822,8 +804,8 @@ def show_search_history(ui_lang):
822
  html += f'''<tr style="background:#FFF;border-bottom:2px solid #1F2937">
823
  <td style="padding:12px;font-weight:700;color:#1F2937">{kw}</td>
824
  <td style="padding:12px;color:#1F2937">{country}</td>
825
- <td style="padding:12px;color:#1F2937">{lang[:10]}</td>
826
- <td style="padding:12px;color:#1F2937">{sort_by[:10]}</td>
827
  <td style="padding:12px;color:#3B82F6;font-weight:700">{cnt}</td>
828
  <td style="padding:12px;font-size:12px;color:#666">{searched[:16].replace("T"," ")}</td>
829
  </tr>'''
@@ -856,9 +838,9 @@ def switch_ui_language(ui_lang):
856
  initial_trending = get_real_trending_keywords("US", "en")
857
 
858
  # ============================================
859
- # 🎨 Gradio UI
860
  # ============================================
861
- with gr.Blocks(css=css, title="YouTube Trend Analyzer") as demo:
862
  ui_lang_state = gr.State("en")
863
 
864
  gr.HTML('''<div style="text-align:center;margin:20px 0">
@@ -866,11 +848,13 @@ with gr.Blocks(css=css, title="YouTube Trend Analyzer") as demo:
866
  <img src="https://img.shields.io/static/v1?label=🏠 HOME&message=HUMANGEN.AI&color=0000ff&labelColor=ffcc00&style=for-the-badge">
867
  </a></div>''')
868
 
869
- gr.Markdown("# 🎬 YOUTUBE TREND ANALYZER 📊", elem_classes="header-text")
870
 
871
  with gr.Row():
872
- db_stats = gr.Markdown("📊 Loading...", scale=4)
873
- ui_lang_dropdown = gr.Dropdown(choices=["English", "한국어"], value="English", label="🌐 UI Language", scale=1, interactive=True)
 
 
874
 
875
  with gr.Tabs():
876
  with gr.Tab("🔍 Search"):
@@ -909,29 +893,29 @@ with gr.Blocks(css=css, title="YouTube Trend Analyzer") as demo:
909
  with gr.Tab("🏷️ Keyword Suggest"):
910
  kw_input = gr.Textbox(label="Enter base keyword", placeholder="e.g., Python tutorial")
911
  kw_btn = gr.Button("🔍 Generate Keywords", variant="primary")
912
- kw_output = gr.Textbox(label="Suggested Keywords", lines=20, elem_classes="llm-result")
913
 
914
  with gr.Tab("🔮 Trend Prediction"):
915
  tp_input = gr.Textbox(label="Enter topic", placeholder="e.g., AI tools")
916
  tp_btn = gr.Button("🔮 Predict Trend", variant="primary")
917
- tp_output = gr.Textbox(label="Trend Analysis", lines=20, elem_classes="llm-result")
918
 
919
  with gr.Tab("💡 Content Ideas"):
920
  ci_input = gr.Textbox(label="Enter topic", placeholder="e.g., Home workout")
921
  ci_btn = gr.Button("💡 Generate Ideas", variant="primary")
922
- ci_output = gr.Textbox(label="Content Ideas", lines=25, elem_classes="llm-result")
923
 
924
  with gr.Tab("📊 Channel Analysis"):
925
  ca_input = gr.Textbox(label="Enter channel/niche", placeholder="e.g., Tech reviews")
926
  ca_btn = gr.Button("📊 Analyze", variant="primary")
927
- ca_output = gr.Textbox(label="Analysis", lines=25, elem_classes="llm-result")
928
 
929
  with gr.Tab("⚔️ Competitor"):
930
  with gr.Row():
931
  comp_my = gr.Textbox(label="Your Channel", placeholder="My channel")
932
  comp_rival = gr.Textbox(label="Competitor", placeholder="Competitor")
933
  comp_btn = gr.Button("⚔️ Compare", variant="primary")
934
- comp_output = gr.Textbox(label="Analysis", lines=25, elem_classes="llm-result")
935
 
936
  with gr.Tab("🕐 History"):
937
  history_btn = gr.Button("🔄 Refresh", variant="primary")
@@ -960,4 +944,6 @@ with gr.Blocks(css=css, title="YouTube Trend Analyzer") as demo:
960
  ca_btn.click(analyze_channel, [ca_input, ui_lang_state], ca_output)
961
  comp_btn.click(analyze_competitor, [comp_my, comp_rival, ui_lang_state], comp_output)
962
 
963
- demo.launch()
 
 
 
81
  }
82
 
83
  # ============================================
84
+ # 🎨 CSS
85
  # ============================================
86
  css = """
87
  @import url('https://fonts.googleapis.com/css2?family=Bangers&family=Comic+Neue:wght@400;700&display=swap');
 
167
  ::-webkit-scrollbar-thumb { background: #3B82F6; border: 2px solid #1F2937; }
168
  ::selection { background: #FACC15; color: #1F2937; }
169
 
170
+ /* LLM Result box */
171
  .llm-result textarea {
172
  background: #1F2937 !important;
173
  color: #10B981 !important;
 
360
  return kw if kw else ""
361
 
362
  # ============================================
363
+ # 🔍 Main Search Function
364
  # ============================================
365
  def search_videos(keyword, country, language, sort_by, date_filter, max_results, ui_lang):
366
  L = UI_LANG.get(ui_lang, UI_LANG["en"])
367
  if not keyword or not keyword.strip():
368
+ return f"⚠️ {L['no_keyword']}", "📊 DB: -"
369
 
370
  max_results = int(max_results)
371
  all_items, next_page = [], None
 
388
  try:
389
  resp = youtube.search().list(**params).execute()
390
  except Exception as e:
391
+ return f"API Error: {e}", "📊 DB: Error"
392
  items = resp.get("items", [])
393
  if not items: break
394
  all_items.extend(items)
 
396
  if not next_page: break
397
 
398
  if not all_items:
399
+ return f"{L['no_results']}", "📊 DB: -"
400
 
401
  video_ids = [item["id"]["videoId"] for item in all_items]
402
  channel_ids = list(set([item["snippet"]["channelId"] for item in all_items]))
 
432
  ai_ratings = get_ai_pick_rating(videos_data)
433
  save_to_db(videos_data, channel_subs_raw, keyword, country, language, sort_by)
434
 
 
435
  uid = str(uuid.uuid4()).replace("-", "")[:8]
436
 
437
  html = f'''
 
439
  #tbl_{uid} {{ width:100%; border-collapse:collapse; font-family:'Comic Neue',cursive; }}
440
  #tbl_{uid} th {{
441
  background:#EF4444; color:#fff; padding:12px 6px; border:2px solid #1F2937;
442
+ font-family:'Bangers',cursive; cursor:pointer; user-select:none;
443
  }}
444
  #tbl_{uid} th:hover {{ background:#DC2626; }}
445
+ #tbl_{uid} th.sort-asc::after {{ content:" ▲"; color:#FACC15; }}
446
+ #tbl_{uid} th.sort-desc::after {{ content:" ▼"; color:#FACC15; }}
447
  #tbl_{uid} td {{ padding:8px 6px; border-bottom:2px solid #1F2937; background:#FFF; vertical-align:middle; }}
448
  #tbl_{uid} tr:hover td {{ background:#FEF9C3; }}
449
  #tbl_{uid} img {{ border-radius:4px; border:2px solid #1F2937; }}
 
459
  🎬 {L["total"]} <b>{len(videos_data)}</b> {L["results"]} | 🔍 "{keyword}" | 🌍 {country}
460
  <br><span style="font-size:0.85rem">🤖 AI Pick: ♥ ⭐ ⭐⭐ ⭐⭐⭐ | 💡 {"헤더 클릭 = 정렬" if ui_lang=="ko" else "Click header to sort"}</span>
461
  <div style="margin-top:10px">
462
+ <button class="sortbtn_{uid}" onclick="doSort_{uid}(4,'n')">{L["subs"]}</button>
463
+ <button class="sortbtn_{uid}" onclick="doSort_{uid}(5,'n')">{L["views"]}</button>
464
+ <button class="sortbtn_{uid}" onclick="doSort_{uid}(6,'n')">{L["likes"]}</button>
465
+ <button class="sortbtn_{uid}" onclick="doSort_{uid}(7,'n')">{L["comments"]}</button>
466
+ <button class="sortbtn_{uid}" onclick="doSort_{uid}(8,'n')">AI</button>
467
+ <button class="sortbtn_{uid}" onclick="doSort_{uid}(9,'s')">{L["date"]}</button>
468
  </div>
469
  </div>
470
 
471
  <div style="max-height:700px; overflow-y:auto; border:3px solid #1F2937; border-radius:8px;">
472
  <table id="tbl_{uid}">
473
  <thead><tr>
474
+ <th style="width:45px">{L["rank"]}</th>
475
  <th style="width:120px">{L["thumb"]}</th>
476
+ <th>{L["title_col"]}</th>
477
+ <th style="width:100px">{L["channel"]}</th>
478
+ <th onclick="doSort_{uid}(4,'n')" style="width:70px;cursor:pointer">{L["subs"]}</th>
479
+ <th onclick="doSort_{uid}(5,'n')" style="width:75px;cursor:pointer">{L["views"]}</th>
480
+ <th onclick="doSort_{uid}(6,'n')" style="width:60px;cursor:pointer">{L["likes"]}</th>
481
+ <th onclick="doSort_{uid}(7,'n')" style="width:55px;cursor:pointer">{L["comments"]}</th>
482
+ <th onclick="doSort_{uid}(8,'n')" style="width:65px;cursor:pointer">{L["ai_pick_col"]}</th>
483
+ <th onclick="doSort_{uid}(9,'s')" style="width:90px;cursor:pointer">{L["date"]}</th>
484
  </tr></thead>
485
  <tbody>
486
  '''
 
506
  <td data-v="{v['published_at'][:10]}">{v['published_at'][:10]}</td>
507
  </tr>'''
508
 
 
509
  html += f'''
510
  </tbody>
511
  </table>
 
513
 
514
  <script>
515
  (function() {{
516
+ var sortState_{uid} = {{}};
 
517
 
518
+ window.doSort_{uid} = function(colIdx, type) {{
519
+ var tbl = document.getElementById('tbl_{uid}');
520
+ if (!tbl) return;
521
+
522
  var tbody = tbl.querySelector('tbody');
523
  var rows = Array.from(tbody.querySelectorAll('tr'));
524
  var headers = tbl.querySelectorAll('th');
525
 
526
+ var asc = sortState_{uid}[colIdx] !== 'asc';
527
+ sortState_{uid} = {{}};
528
+ sortState_{uid}[colIdx] = asc ? 'asc' : 'desc';
529
 
530
+ headers.forEach(function(h) {{ h.classList.remove('sort-asc', 'sort-desc'); }});
531
+ if (headers[colIdx]) headers[colIdx].classList.add(asc ? 'sort-asc' : 'sort-desc');
532
 
533
  rows.sort(function(a, b) {{
534
  var aCell = a.cells[colIdx];
 
548
  }});
549
 
550
  rows.forEach(function(row) {{ tbody.appendChild(row); }});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
551
  }};
552
  }})();
553
  </script>
 
557
  return html, f"📊 DB: Videos {stats['videos']} | Records {stats['stats']} | Channels {stats['channels']} | Searches {stats['searches']}"
558
 
559
  # ============================================
560
+ # 🔥 Trending Alerts
561
  # ============================================
562
  def show_trending_alerts(ui_lang):
563
  is_ko = ui_lang == "ko"
 
612
  return html + '</div>'
613
 
614
  # ============================================
615
+ # 📈 Top Growing
616
  # ============================================
617
  def show_top_growing(ui_lang):
618
  is_ko = ui_lang == "ko"
 
677
  return html + '</div>'
678
 
679
  # ============================================
680
+ # ⭐ AI Pick
681
  # ============================================
682
  def show_ai_picks(ui_lang):
683
  is_ko = ui_lang == "ko"
 
693
  top_picks = [(i, v, ratings.get(i, 0)) for i, v in enumerate(videos) if ratings.get(i, 0) >= 3]
694
  top_picks.sort(key=lambda x: (-x[2], -x[1]['views']))
695
 
 
696
  analysis_html = ""
697
  if groq_client and top_picks:
698
  info = "\n".join([f"- {v['title'][:50]} (Views:{format_count(v['views'])})" for _, v, _ in top_picks[:5]])
 
708
  <div style="background:linear-gradient(135deg,#FACC15,#F59E0B);padding:20px;border:3px solid #1F2937;border-radius:8px;margin-bottom:15px">
709
  <h2 style="font-family:'Bangers',cursive;color:#1F2937;margin:0;text-shadow:2px 2px 0 #FFF;font-size:2rem">⭐ {"AI 추천 - TOP 영상" if is_ko else "AI PICK - TOP RECOMMENDATIONS"}</h2>
710
  <p style="color:#1F2937;margin:10px 0 0;font-family:'Comic Neue',cursive;font-weight:700;font-size:15px">
711
+ {"⭐⭐ 이상 등급 영상" if is_ko else "⭐⭐+ rated videos"}: <b>{len(top_picks)}</b>{"개" if is_ko else " videos"}
712
  </p>
713
  </div>
714
  {analysis_html}
 
804
  html += f'''<tr style="background:#FFF;border-bottom:2px solid #1F2937">
805
  <td style="padding:12px;font-weight:700;color:#1F2937">{kw}</td>
806
  <td style="padding:12px;color:#1F2937">{country}</td>
807
+ <td style="padding:12px;color:#1F2937">{lang[:10] if lang else "-"}</td>
808
+ <td style="padding:12px;color:#1F2937">{sort_by[:10] if sort_by else "-"}</td>
809
  <td style="padding:12px;color:#3B82F6;font-weight:700">{cnt}</td>
810
  <td style="padding:12px;font-size:12px;color:#666">{searched[:16].replace("T"," ")}</td>
811
  </tr>'''
 
838
  initial_trending = get_real_trending_keywords("US", "en")
839
 
840
  # ============================================
841
+ # 🎨 Gradio UI (Gradio 6.0 Compatible)
842
  # ============================================
843
+ with gr.Blocks(title="YouTube Trend Analyzer") as demo:
844
  ui_lang_state = gr.State("en")
845
 
846
  gr.HTML('''<div style="text-align:center;margin:20px 0">
 
848
  <img src="https://img.shields.io/static/v1?label=🏠 HOME&message=HUMANGEN.AI&color=0000ff&labelColor=ffcc00&style=for-the-badge">
849
  </a></div>''')
850
 
851
+ gr.Markdown("# 🎬 YOUTUBE TREND ANALYZER 📊", elem_classes=["header-text"])
852
 
853
  with gr.Row():
854
+ with gr.Column(scale=4):
855
+ db_stats = gr.Markdown("📊 Loading...")
856
+ with gr.Column(scale=1):
857
+ ui_lang_dropdown = gr.Dropdown(choices=["English", "한국어"], value="English", label="🌐 UI Language", interactive=True)
858
 
859
  with gr.Tabs():
860
  with gr.Tab("🔍 Search"):
 
893
  with gr.Tab("🏷️ Keyword Suggest"):
894
  kw_input = gr.Textbox(label="Enter base keyword", placeholder="e.g., Python tutorial")
895
  kw_btn = gr.Button("🔍 Generate Keywords", variant="primary")
896
+ kw_output = gr.Textbox(label="Suggested Keywords", lines=20, elem_classes=["llm-result"])
897
 
898
  with gr.Tab("🔮 Trend Prediction"):
899
  tp_input = gr.Textbox(label="Enter topic", placeholder="e.g., AI tools")
900
  tp_btn = gr.Button("🔮 Predict Trend", variant="primary")
901
+ tp_output = gr.Textbox(label="Trend Analysis", lines=20, elem_classes=["llm-result"])
902
 
903
  with gr.Tab("💡 Content Ideas"):
904
  ci_input = gr.Textbox(label="Enter topic", placeholder="e.g., Home workout")
905
  ci_btn = gr.Button("💡 Generate Ideas", variant="primary")
906
+ ci_output = gr.Textbox(label="Content Ideas", lines=25, elem_classes=["llm-result"])
907
 
908
  with gr.Tab("📊 Channel Analysis"):
909
  ca_input = gr.Textbox(label="Enter channel/niche", placeholder="e.g., Tech reviews")
910
  ca_btn = gr.Button("📊 Analyze", variant="primary")
911
+ ca_output = gr.Textbox(label="Analysis", lines=25, elem_classes=["llm-result"])
912
 
913
  with gr.Tab("⚔️ Competitor"):
914
  with gr.Row():
915
  comp_my = gr.Textbox(label="Your Channel", placeholder="My channel")
916
  comp_rival = gr.Textbox(label="Competitor", placeholder="Competitor")
917
  comp_btn = gr.Button("⚔️ Compare", variant="primary")
918
+ comp_output = gr.Textbox(label="Analysis", lines=25, elem_classes=["llm-result"])
919
 
920
  with gr.Tab("🕐 History"):
921
  history_btn = gr.Button("🔄 Refresh", variant="primary")
 
944
  ca_btn.click(analyze_channel, [ca_input, ui_lang_state], ca_output)
945
  comp_btn.click(analyze_competitor, [comp_my, comp_rival, ui_lang_state], comp_output)
946
 
947
+ # Launch with CSS (Gradio 6.0 style)
948
+ demo.launch(css=css)
949
+