Update app.py
Browse files
app.py
CHANGED
|
@@ -167,9 +167,7 @@ Examples:
|
|
| 167 |
except Exception as e:
|
| 168 |
print(f"Gemini global title parsing error: {e}")
|
| 169 |
return None
|
| 170 |
-
|
| 171 |
def extract_person_from_description_with_gemini(self, description: str) -> Optional[str]:
|
| 172 |
-
"""Extract a person's name from the description using Gemini (global, priority 2)"""
|
| 173 |
if not description or len(description.strip()) < 10 or not model:
|
| 174 |
return None
|
| 175 |
|
|
@@ -703,104 +701,29 @@ Note: Do not restrict by nationality, era, or field. Consider notable people wor
|
|
| 703 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 704 |
<title>Recent Videos - {time_range_text}</title>
|
| 705 |
<style>
|
| 706 |
-
body {{ font-family: 'Helvetica Neue', Arial, sans-serif; margin: 0; padding: 20px; background-color: #
|
| 707 |
-
.container {{ max-width: 1200px; margin: 0 auto; }}
|
| 708 |
-
h1 {{ color: #
|
| 709 |
-
.stats-info {{
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
}}
|
| 718 |
-
.
|
| 719 |
-
|
| 720 |
-
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
| 726 |
-
|
| 727 |
-
}}
|
| 728 |
-
.
|
| 729 |
-
.video-item.Important {{ border-left-color: #ff9800; background-color: #fff3e0; }}
|
| 730 |
-
.thumbnail {{
|
| 731 |
-
width: 160px;
|
| 732 |
-
height: 90px;
|
| 733 |
-
object-fit: cover;
|
| 734 |
-
margin-right: 15px;
|
| 735 |
-
border-radius: 4px;
|
| 736 |
-
flex-shrink: 0;
|
| 737 |
-
}}
|
| 738 |
-
.video-info {{ flex: 1; }}
|
| 739 |
-
.video-title {{
|
| 740 |
-
font-weight: bold;
|
| 741 |
-
margin-bottom: 8px;
|
| 742 |
-
font-size: 16px;
|
| 743 |
-
line-height: 1.4;
|
| 744 |
-
}}
|
| 745 |
-
.video-meta {{
|
| 746 |
-
color: #666;
|
| 747 |
-
font-size: 14px;
|
| 748 |
-
line-height: 1.6;
|
| 749 |
-
margin-bottom: 5px;
|
| 750 |
-
}}
|
| 751 |
-
.channel-info {{
|
| 752 |
-
display: flex;
|
| 753 |
-
align-items: center;
|
| 754 |
-
margin-bottom: 8px;
|
| 755 |
-
}}
|
| 756 |
-
.channel-icon {{
|
| 757 |
-
width: 24px;
|
| 758 |
-
height: 24px;
|
| 759 |
-
border-radius: 50%;
|
| 760 |
-
margin-right: 8px;
|
| 761 |
-
}}
|
| 762 |
-
.stats {{ color: #2196f3; font-weight: bold; }}
|
| 763 |
-
.importance-badge {{
|
| 764 |
-
display: inline-block;
|
| 765 |
-
padding: 2px 8px;
|
| 766 |
-
border-radius: 12px;
|
| 767 |
-
font-size: 12px;
|
| 768 |
-
font-weight: bold;
|
| 769 |
-
margin-left: 10px;
|
| 770 |
-
}}
|
| 771 |
-
.importance-badge.Critical {{ background: #f44336; color: white; }}
|
| 772 |
-
.importance-badge.Important {{ background: #ff9800; color: white; }}
|
| 773 |
-
.importance-badge.Normal {{ background: #4caf50; color: white; }}
|
| 774 |
-
.detection-badge {{
|
| 775 |
-
display: inline-block;
|
| 776 |
-
padding: 2px 6px;
|
| 777 |
-
border-radius: 8px;
|
| 778 |
-
font-size: 11px;
|
| 779 |
-
background: #2196f3;
|
| 780 |
-
color: white;
|
| 781 |
-
margin-left: 5px;
|
| 782 |
-
}}
|
| 783 |
-
.video-links {{ margin-top: 8px; }}
|
| 784 |
-
.video-links a {{
|
| 785 |
-
display: inline-block;
|
| 786 |
-
margin-right: 15px;
|
| 787 |
-
color: #1976d2;
|
| 788 |
-
text-decoration: none;
|
| 789 |
-
font-size: 14px;
|
| 790 |
-
padding: 4px 8px;
|
| 791 |
-
border: 1px solid #1976d2;
|
| 792 |
-
border-radius: 4px;
|
| 793 |
-
transition: background-color 0.3s;
|
| 794 |
-
}}
|
| 795 |
-
.video-links a:hover {{
|
| 796 |
-
background-color: #e3f2fd;
|
| 797 |
-
}}
|
| 798 |
-
.no-videos {{
|
| 799 |
-
text-align: center;
|
| 800 |
-
padding: 40px;
|
| 801 |
-
color: #666;
|
| 802 |
-
font-size: 16px;
|
| 803 |
-
}}
|
| 804 |
</style>
|
| 805 |
</head>
|
| 806 |
<body>
|
|
@@ -896,57 +819,30 @@ Note: Do not restrict by nationality, era, or field. Consider notable people wor
|
|
| 896 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 897 |
<title>YouTube Competitor Analysis Dashboard (Global)</title>
|
| 898 |
<style>
|
| 899 |
-
body { font-family: 'Helvetica Neue', Arial, sans-serif; margin: 0; padding: 20px; background-color: #
|
| 900 |
-
.container { max-width: 1200px; margin: 0 auto; }
|
| 901 |
-
h1 { color: #
|
| 902 |
-
h2 { border-bottom: 2px solid
|
| 903 |
-
.section { background:
|
| 904 |
-
.trend-item { border: 1px solid #
|
| 905 |
-
.trend-title { font-size: 18px; font-weight: bold; color: #
|
| 906 |
-
.trend-meta { font-size: 14px; color: #
|
| 907 |
-
.video-item { display: flex; align-items: flex-start; margin-bottom: 15px; padding: 10px; border-left: 4px solid #
|
| 908 |
-
.video-item.Critical { border-left-color: #
|
| 909 |
-
.video-item.Important { border-left-color: #
|
| 910 |
-
.thumbnail { width: 160px; height: 90px; object-fit: cover; margin-right: 15px; border-radius: 4px; flex-shrink: 0; }
|
| 911 |
-
.video-info { flex: 1; }
|
| 912 |
-
.video-title { font-weight: bold; margin-bottom: 5px; font-size: 16px; }
|
| 913 |
-
.video-meta { color: #
|
| 914 |
-
.importance-badge {
|
| 915 |
-
|
| 916 |
-
|
| 917 |
-
|
| 918 |
-
|
| 919 |
-
|
| 920 |
-
|
| 921 |
-
}
|
| 922 |
-
.
|
| 923 |
-
.importance-badge.Important { background: #ff9800; color: white; }
|
| 924 |
-
.importance-badge.Normal { background: #4caf50; color: white; }
|
| 925 |
-
.detection-badge {
|
| 926 |
-
display: inline-block;
|
| 927 |
-
padding: 2px 6px;
|
| 928 |
-
border-radius: 8px;
|
| 929 |
-
font-size: 11px;
|
| 930 |
-
background: #2196f3;
|
| 931 |
-
color: white;
|
| 932 |
-
margin-left: 5px;
|
| 933 |
-
}
|
| 934 |
-
.stats { color: #2196f3; font-weight: bold; }
|
| 935 |
-
.video-links { margin-top: 8px; }
|
| 936 |
-
.video-links a {
|
| 937 |
-
display: inline-block;
|
| 938 |
-
margin-right: 15px;
|
| 939 |
-
color: #1976d2;
|
| 940 |
-
text-decoration: none;
|
| 941 |
-
font-size: 14px;
|
| 942 |
-
padding: 4px 8px;
|
| 943 |
-
border: 1px solid #1976d2;
|
| 944 |
-
border-radius: 4px;
|
| 945 |
-
transition: background-color 0.3s;
|
| 946 |
-
}
|
| 947 |
-
.video-links a:hover {
|
| 948 |
-
background-color: #e3f2fd;
|
| 949 |
-
}
|
| 950 |
</style>
|
| 951 |
</head>
|
| 952 |
<body>
|
|
@@ -1031,42 +927,19 @@ Note: Do not restrict by nationality, era, or field. Consider notable people wor
|
|
| 1031 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 1032 |
<title>Channel Management (Global)</title>
|
| 1033 |
<style>
|
| 1034 |
-
body { font-family: 'Helvetica Neue', Arial, sans-serif; margin: 0; padding: 20px; background-color: #
|
| 1035 |
-
.container { max-width: 800px; margin: 0 auto; }
|
| 1036 |
-
h2 { border-bottom: 2px solid
|
| 1037 |
-
.channel-item {
|
| 1038 |
-
|
| 1039 |
-
|
| 1040 |
-
|
| 1041 |
-
|
| 1042 |
-
|
| 1043 |
-
|
| 1044 |
-
|
| 1045 |
-
}
|
| 1046 |
-
.
|
| 1047 |
-
width: 48px;
|
| 1048 |
-
height: 48px;
|
| 1049 |
-
border-radius: 50%;
|
| 1050 |
-
margin-right: 15px;
|
| 1051 |
-
object-fit: cover;
|
| 1052 |
-
}
|
| 1053 |
-
.channel-info { flex: 1; }
|
| 1054 |
-
.channel-name { font-weight: bold; font-size: 16px; margin-bottom: 5px; }
|
| 1055 |
-
.channel-meta { color: #666; font-size: 14px; }
|
| 1056 |
-
.channel-actions { display: flex; gap: 10px; }
|
| 1057 |
-
.btn {
|
| 1058 |
-
padding: 6px 12px;
|
| 1059 |
-
border: none;
|
| 1060 |
-
border-radius: 4px;
|
| 1061 |
-
cursor: pointer;
|
| 1062 |
-
font-size: 12px;
|
| 1063 |
-
text-decoration: none;
|
| 1064 |
-
display: inline-block;
|
| 1065 |
-
transition: opacity 0.3s;
|
| 1066 |
-
}
|
| 1067 |
-
.btn-edit { background: #2196f3; color: white; }
|
| 1068 |
-
.btn-delete { background: #f44336; color: white; }
|
| 1069 |
-
.btn:hover { opacity: 0.8; }
|
| 1070 |
</style>
|
| 1071 |
</head>
|
| 1072 |
<body>
|
|
@@ -1125,6 +998,42 @@ Note: Do not restrict by nationality, era, or field. Consider notable people wor
|
|
| 1125 |
# アプリのインスタンスを作成
|
| 1126 |
analyzer = YouTubeCompetitorAnalyzer()
|
| 1127 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1128 |
# Gradio インターface
|
| 1129 |
def add_channel_interface(channel_ids_text):
|
| 1130 |
"""Interface function that supports adding multiple channel IDs"""
|
|
@@ -1187,7 +1096,7 @@ def show_recent_videos_interface(hours_selection, limit_selection):
|
|
| 1187 |
return analyzer.generate_recent_videos_html(hours, limit)
|
| 1188 |
|
| 1189 |
# Gradioアプリの構築
|
| 1190 |
-
with gr.Blocks(title="YouTube Competitor Analysis (Global)", theme=gr.themes.
|
| 1191 |
gr.Markdown("# 🌍 YouTube Competitor Analysis App (Global)")
|
| 1192 |
gr.Markdown("Analyze competitor channel uploads and detect global clustered trends using **Gemini 2.5 Flash**.")
|
| 1193 |
|
|
|
|
| 167 |
except Exception as e:
|
| 168 |
print(f"Gemini global title parsing error: {e}")
|
| 169 |
return None
|
|
|
|
| 170 |
def extract_person_from_description_with_gemini(self, description: str) -> Optional[str]:
|
|
|
|
| 171 |
if not description or len(description.strip()) < 10 or not model:
|
| 172 |
return None
|
| 173 |
|
|
|
|
| 701 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 702 |
<title>Recent Videos - {time_range_text}</title>
|
| 703 |
<style>
|
| 704 |
+
body {{ font-family: 'Helvetica Neue', Arial, sans-serif; margin: 0; padding: 20px; background-color: #000000 !important; color: #ffffff !important; }}
|
| 705 |
+
.container {{ max-width: 1200px; margin: 0 auto; background-color: transparent !important; }}
|
| 706 |
+
h1 {{ color: #ffffff !important; text-align: center; margin-bottom: 30px; }}
|
| 707 |
+
.stats-info {{ background: rgba(255,255,255,0.03) !important; padding: 15px !important; border-radius: 8px !important; margin-bottom: 20px !important; text-align: center !important; font-size: 16px !important; color: #bcdffb !important; }}
|
| 708 |
+
.video-item {{ display: flex !important; align-items: flex-start !important; background: #0b0b0b !important; margin-bottom: 15px !important; padding: 15px !important; border-radius: 8px !important; box-shadow: 0 2px 6px rgba(0,0,0,0.6) !important; border-left: 4px solid #222 !important; }}
|
| 709 |
+
.video-item.Critical {{ border-left-color: #b71c1c !important; background-color: #120202 !important; }}
|
| 710 |
+
.video-item.Important {{ border-left-color: #bf360c !important; background-color: #241100 !important; }}
|
| 711 |
+
.thumbnail {{ width: 160px !important; height: 90px !important; object-fit: cover !important; margin-right: 15px !important; border-radius: 4px !important; flex-shrink: 0 !important; }}
|
| 712 |
+
.video-info {{ flex: 1 !important; }}
|
| 713 |
+
.video-title {{ font-weight: bold !important; margin-bottom: 8px !important; font-size: 16px !important; line-height: 1.4 !important; color: #ffffff !important; }}
|
| 714 |
+
.video-meta {{ color: #cfcfcf !important; font-size: 14px !important; line-height: 1.6 !important; margin-bottom: 5px !important; }}
|
| 715 |
+
.channel-info {{ display: flex !important; align-items: center !important; margin-bottom: 8px !important; }}
|
| 716 |
+
.channel-icon {{ width: 24px !important; height: 24px !important; border-radius: 50% !important; margin-right: 8px !important; }}
|
| 717 |
+
.stats {{ color: #64b5f6 !important; font-weight: bold !important; }}
|
| 718 |
+
.importance-badge {{ display: inline-block !important; padding: 2px 8px !important; border-radius: 12px !important; font-size: 12px !important; font-weight: bold !important; margin-left: 10px !important; }}
|
| 719 |
+
.importance-badge.Critical {{ background: #b71c1c !important; color: white !important; }}
|
| 720 |
+
.importance-badge.Important {{ background: #bf360c !important; color: white !important; }}
|
| 721 |
+
.importance-badge.Normal {{ background: #2e7d32 !important; color: white !important; }}
|
| 722 |
+
.detection-badge {{ display: inline-block !important; padding: 2px 6px !important; border-radius: 8px !important; font-size: 11px !important; background: #1976d2 !important; color: white !important; margin-left: 5px !important; }}
|
| 723 |
+
.video-links {{ margin-top: 8px !important; }}
|
| 724 |
+
.video-links a {{ display: inline-block !important; margin-right: 15px !important; color: #90caf9 !important; text-decoration: none !important; font-size: 14px !important; padding: 4px 8px !important; border: 1px solid #263238 !important; border-radius: 4px !important; transition: background-color 0.3s !important; }}
|
| 725 |
+
.video-links a:hover {{ background-color: rgba(227,242,253,0.04) !important; }}
|
| 726 |
+
.no-videos {{ text-align: center !important; padding: 40px !important; color: #bdbdbd !important; font-size: 16px !important; }}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 727 |
</style>
|
| 728 |
</head>
|
| 729 |
<body>
|
|
|
|
| 819 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 820 |
<title>YouTube Competitor Analysis Dashboard (Global)</title>
|
| 821 |
<style>
|
| 822 |
+
body {{ font-family: 'Helvetica Neue', Arial, sans-serif; margin: 0; padding: 20px; background-color: #000000 !important; color: #ffffff !important; }}
|
| 823 |
+
.container {{ max-width: 1200px; margin: 0 auto; background-color: transparent !important; }}
|
| 824 |
+
h1 {{ color: #ffffff !important; text-align: center; margin-bottom: 30px; }}
|
| 825 |
+
h2 {{ border-bottom: 2px solid rgba(255,255,255,0.06) !important; padding-bottom: 10px !important; }}
|
| 826 |
+
.section {{ background: #070707 !important; margin-bottom: 30px !important; padding: 20px !important; border-radius: 8px !important; box-shadow: 0 2px 6px rgba(0,0,0,0.6) !important; }}
|
| 827 |
+
.trend-item {{ border: 1px solid #1f1f1f !important; margin-bottom: 15px !important; padding: 15px !important; border-radius: 5px !important; background: #0b0b0b !important; }}
|
| 828 |
+
.trend-title {{ font-size: 18px !important; font-weight: bold !important; color: #ff8a80 !important; margin-bottom: 10px !important; }}
|
| 829 |
+
.trend-meta {{ font-size: 14px !important; color: #bdbdbd !important; margin-bottom: 5px !important; }}
|
| 830 |
+
.video-item {{ display: flex !important; align-items: flex-start !important; margin-bottom: 15px !important; padding: 10px !important; border-left: 4px solid #222 !important; }}
|
| 831 |
+
.video-item.Critical {{ border-left-color: #b71c1c !important; background-color: #120202 !important; }}
|
| 832 |
+
.video-item.Important {{ border-left-color: #bf360c !important; background-color: #241100 !important; }}
|
| 833 |
+
.thumbnail {{ width: 160px !important; height: 90px !important; object-fit: cover !important; margin-right: 15px !important; border-radius: 4px !important; flex-shrink: 0 !important; }}
|
| 834 |
+
.video-info {{ flex: 1 !important; }}
|
| 835 |
+
.video-title {{ font-weight: bold !important; margin-bottom: 5px !important; font-size: 16px !important; color: #ffffff !important; }}
|
| 836 |
+
.video-meta {{ color: #cfcfcf !important; font-size: 14px !important; line-height: 1.6 !important; }}
|
| 837 |
+
.importance-badge {{ display: inline-block !important; padding: 2px 8px !important; border-radius: 12px !important; font-size: 12px !important; font-weight: bold !important; margin-left: 10px !important; }}
|
| 838 |
+
.importance-badge.Critical {{ background: #b71c1c !important; color: white !important; }}
|
| 839 |
+
.importance-badge.Important {{ background: #bf360c !important; color: white !important; }}
|
| 840 |
+
.importance-badge.Normal {{ background: #2e7d32 !important; color: white !important; }}
|
| 841 |
+
.detection-badge {{ display: inline-block !important; padding: 2px 6px !important; border-radius: 8px !important; font-size: 11px !important; background: #1976d2 !important; color: white !important; margin-left: 5px !important; }}
|
| 842 |
+
.stats {{ color: #64b5f6 !important; font-weight: bold !important; }}
|
| 843 |
+
.video-links {{ margin-top: 8px !important; }}
|
| 844 |
+
.video-links a {{ display: inline-block !important; margin-right: 15px !important; color: #90caf9 !important; text-decoration: none !important; font-size: 14px !important; padding: 4px 8px !important; border: 1px solid #263238 !important; border-radius: 4px !important; transition: background-color 0.3s !important; }}
|
| 845 |
+
.video-links a:hover {{ background-color: rgba(227,242,253,0.04) !important; }}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 846 |
</style>
|
| 847 |
</head>
|
| 848 |
<body>
|
|
|
|
| 927 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 928 |
<title>Channel Management (Global)</title>
|
| 929 |
<style>
|
| 930 |
+
body {{ font-family: 'Helvetica Neue', Arial, sans-serif; margin: 0; padding: 20px; background-color: #000000 !important; color: #ffffff !important; }}
|
| 931 |
+
.container {{ max-width: 800px; margin: 0 auto; background-color: transparent !important; }}
|
| 932 |
+
h2 {{ border-bottom: 2px solid rgba(255,255,255,0.06) !important; padding-bottom: 10px !important; }}
|
| 933 |
+
.channel-item {{ display: flex !important; align-items: center !important; background: #0b0b0b !important; margin-bottom: 15px !important; padding: 15px !important; border-radius: 8px !important; box-shadow: 0 2px 6px rgba(0,0,0,0.6) !important; }}
|
| 934 |
+
.channel-icon {{ width: 48px !important; height: 48px !important; border-radius: 50% !important; margin-right: 15px !important; object-fit: cover !important; }}
|
| 935 |
+
.channel-info {{ flex: 1 !important; }}
|
| 936 |
+
.channel-name {{ font-weight: bold !important; font-size: 16px !important; margin-bottom: 5px !important; color: #ffffff !important; }}
|
| 937 |
+
.channel-meta {{ color: #bdbdbd !important; font-size: 14px !important; }}
|
| 938 |
+
.channel-actions {{ display: flex !important; gap: 10px !important; }}
|
| 939 |
+
.btn {{ padding: 6px 12px !important; border: none !important; border-radius: 4px !important; cursor: pointer !important; font-size: 12px !important; text-decoration: none !important; display: inline-block !important; transition: opacity 0.3s !important; }}
|
| 940 |
+
.btn-edit {{ background: #1976d2 !important; color: white !important; border: 1px solid #263238 !important; }}
|
| 941 |
+
.btn-delete {{ background: #b71c1c !important; color: white !important; border: 1px solid #331111 !important; }}
|
| 942 |
+
.btn:hover {{ opacity: 0.9 !important; }}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 943 |
</style>
|
| 944 |
</head>
|
| 945 |
<body>
|
|
|
|
| 998 |
# アプリのインスタンスを作成
|
| 999 |
analyzer = YouTubeCompetitorAnalyzer()
|
| 1000 |
|
| 1001 |
+
# Strong global dark CSS to force dark mode even if hosting injects light styles.
|
| 1002 |
+
# Uses high-specificity selectors and !important to override Hugging Face Spaces' theme.
|
| 1003 |
+
DARK_THEME_CSS = """
|
| 1004 |
+
:root, html, body, .gradio-container {
|
| 1005 |
+
background-color: #000000 !important;
|
| 1006 |
+
color: #ffffff !important;
|
| 1007 |
+
color-scheme: dark !important;
|
| 1008 |
+
}
|
| 1009 |
+
.gradio-container, .gradio-container * {
|
| 1010 |
+
background-color: transparent !important;
|
| 1011 |
+
color: #ffffff !important;
|
| 1012 |
+
border-color: #333333 !important;
|
| 1013 |
+
}
|
| 1014 |
+
/* Inputs, buttons and textareas */
|
| 1015 |
+
button, .gr-button, input, textarea, select, .gradio-textbox, .gradio-file, .gradio-dropdown, .gradio-button {
|
| 1016 |
+
background-color: #0b0b0b !important;
|
| 1017 |
+
color: #ffffff !important;
|
| 1018 |
+
border: 1px solid #333333 !important;
|
| 1019 |
+
}
|
| 1020 |
+
input::placeholder, textarea::placeholder {
|
| 1021 |
+
color: #bfbfbf !important;
|
| 1022 |
+
}
|
| 1023 |
+
.gradio-markdown, .gradio-html, .gradio-label, .gradio-textbox, .gradio-output {
|
| 1024 |
+
color: #ffffff !important;
|
| 1025 |
+
}
|
| 1026 |
+
/* Ensure components that Gradio or Spaces might wrap still show dark backgrounds */
|
| 1027 |
+
.gradio-container .container, .gradio-container .section, .gradio-container .card {
|
| 1028 |
+
background-color: #000000 !important;
|
| 1029 |
+
color: #ffffff !important;
|
| 1030 |
+
}
|
| 1031 |
+
/* Give high contrast to borders and badges */
|
| 1032 |
+
.importance-badge, .detection-badge {
|
| 1033 |
+
color: #ffffff !important;
|
| 1034 |
+
}
|
| 1035 |
+
"""
|
| 1036 |
+
|
| 1037 |
# Gradio インターface
|
| 1038 |
def add_channel_interface(channel_ids_text):
|
| 1039 |
"""Interface function that supports adding multiple channel IDs"""
|
|
|
|
| 1096 |
return analyzer.generate_recent_videos_html(hours, limit)
|
| 1097 |
|
| 1098 |
# Gradioアプリの構築
|
| 1099 |
+
with gr.Blocks(title="YouTube Competitor Analysis (Global)", theme=gr.themes.Dark(), css=DARK_THEME_CSS) as app:
|
| 1100 |
gr.Markdown("# 🌍 YouTube Competitor Analysis App (Global)")
|
| 1101 |
gr.Markdown("Analyze competitor channel uploads and detect global clustered trends using **Gemini 2.5 Flash**.")
|
| 1102 |
|