Spaces:
Running
Running
Update main.py
Browse files
main.py
CHANGED
@@ -99,34 +99,16 @@ APIFY_API_TOKEN = get_secret('APIFY_API_TOKEN')
|
|
99 |
RAPIDAPI_KEY = get_secret('RAPIDAPI_KEY')
|
100 |
WEBHOOK_SECRET = get_secret('WEBHOOK_SECRET')
|
101 |
|
102 |
-
# --- Model Configurations (Specific April
|
103 |
-
# Model Priority
|
104 |
-
# 1.
|
105 |
-
# 2.
|
106 |
-
# 3.
|
107 |
-
# 4.
|
108 |
-
#
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
)
|
113 |
-
GEMINI_PRO_EXP_MODEL = os.environ.get(
|
114 |
-
"GEMINI_PRO_EXP_MODEL",
|
115 |
-
"gemini-2.5-pro-exp-03-25"
|
116 |
-
)
|
117 |
-
GEMINI_FLASH_MODEL = os.environ.get(
|
118 |
-
"GEMINI_FLASH_MODEL",
|
119 |
-
"gemini-2.0-flash-001"
|
120 |
-
)
|
121 |
-
OPENROUTER_DEEPSEEK_MODEL = os.environ.get(
|
122 |
-
"OPENROUTER_DEEPSEEK_MODEL",
|
123 |
-
"deepseek/deepseek-chat-v3-0324:free"
|
124 |
-
)
|
125 |
-
GROQ_LLAMA4_MODEL = os.environ.get(
|
126 |
-
"GROQ_LLAMA4_MODEL",
|
127 |
-
"meta-llama/llama-4-scout-17b-16e-instruct"
|
128 |
-
)
|
129 |
-
|
130 |
|
131 |
APIFY_ACTOR_ID = os.environ.get("APIFY_ACTOR_ID", "1s7eXiaukVuOr4Ueg") # YT Default
|
132 |
APIFY_STRUCTURED_YT_ACTOR_ID = "gpjTCWkGZS1lHc9pR" # YT Fallback 1 (New Structured Extractor)
|
@@ -162,27 +144,10 @@ if not SUPADATA_API_KEY: logger.warning("Optional secret 'SUPADATA_API_KEY' not
|
|
162 |
if not WEBHOOK_SECRET: logger.info("Optional secret 'WEBHOOK_SECRET' not found. Webhook security disabled.")
|
163 |
|
164 |
logger.info("Secret loading and configuration check finished.")
|
165 |
-
logger.info(
|
166 |
-
|
167 |
-
|
168 |
-
)
|
169 |
-
logger.info(
|
170 |
-
f"Summariser 2 (Gemini Pro Exp): "
|
171 |
-
f"{GEMINI_PRO_EXP_MODEL if _gemini_api_enabled else 'DISABLED'}"
|
172 |
-
)
|
173 |
-
logger.info(
|
174 |
-
f"Summariser 3 (Gemini Flash 2.0): "
|
175 |
-
f"{GEMINI_FLASH_MODEL if _gemini_api_enabled else 'DISABLED'}"
|
176 |
-
)
|
177 |
-
logger.info(
|
178 |
-
f"Summariser 4 (OpenRouter DeepSeek V3): "
|
179 |
-
f"{OPENROUTER_DEEPSEEK_MODEL if _openrouter_fallback_enabled else 'DISABLED'}"
|
180 |
-
)
|
181 |
-
logger.info(
|
182 |
-
f"Summariser 5 (Groq Llama 4 Scout): "
|
183 |
-
f"{GROQ_LLAMA4_MODEL if _groq_enabled else 'DISABLED'}"
|
184 |
-
)
|
185 |
-
|
186 |
# --- Updated Logging ---
|
187 |
logger.info(f"Using Apify Actor (YT Fallback 1 - Structured): {APIFY_STRUCTURED_YT_ACTOR_ID}")
|
188 |
logger.info(f"Using Apify Actor (YT Fallback 3 - Default): {APIFY_ACTOR_ID}") # Now fallback 3
|
@@ -887,27 +852,19 @@ async def _call_gemini(text: str, summary_type: str, model_name: str) -> Tuple[O
|
|
887 |
# FIX: Define safety_settings explicitly with the 5 categories from the error log
|
888 |
# Removed the problematic loop that dynamically added potentially unsupported categories.
|
889 |
try:
|
890 |
-
|
891 |
-
|
892 |
-
|
893 |
-
|
894 |
-
|
895 |
-
|
896 |
-
|
897 |
-
]
|
898 |
-
|
899 |
-
list_descr = ", ".join(f"{s['category']}:{s['threshold']}"
|
900 |
-
for s in safety_settings)
|
901 |
-
logger.debug(f"[Gemini {model_name}] Using safety settings → {list_descr}")
|
902 |
-
|
903 |
except AttributeError:
|
904 |
-
|
905 |
-
|
906 |
-
|
907 |
-
|
908 |
-
f"({model_name})."
|
909 |
-
)
|
910 |
-
|
911 |
|
912 |
try:
|
913 |
logger.debug(f"[Gemini {model_name}] Initializing model {model_name}")
|
@@ -1019,91 +976,82 @@ async def _call_openrouter(text: str, summary_type: str) -> Tuple[Optional[str],
|
|
1019 |
|
1020 |
async def generate_summary(text: str, summary_type: str) -> str:
|
1021 |
"""
|
1022 |
-
Generates
|
1023 |
-
1.
|
1024 |
-
2.
|
1025 |
-
3.
|
1026 |
-
4.
|
1027 |
-
|
1028 |
"""
|
1029 |
-
|
|
|
|
|
|
|
1030 |
summary: Optional[str] = None
|
1031 |
errors: Dict[str, Optional[str]] = {
|
1032 |
-
"
|
1033 |
"GeminiProExp": None,
|
1034 |
"GeminiFlash": None,
|
1035 |
"DeepSeekV3": None,
|
1036 |
-
"Llama4Scout": None,
|
1037 |
}
|
1038 |
|
1039 |
-
# Attempt
|
1040 |
-
if
|
1041 |
-
|
1042 |
-
|
1043 |
-
)
|
1044 |
if summary:
|
|
|
1045 |
return summary
|
1046 |
-
|
1047 |
-
f"[Summary
|
1048 |
-
f"{errors['GeminiFlashPreview']}; trying Gemini Pro Exp."
|
1049 |
-
)
|
1050 |
else:
|
1051 |
-
|
|
|
1052 |
|
1053 |
-
# Attempt
|
1054 |
if _gemini_api_enabled:
|
1055 |
-
|
1056 |
-
|
1057 |
-
)
|
1058 |
if summary:
|
|
|
1059 |
return summary
|
1060 |
-
|
1061 |
-
f"[Summary
|
1062 |
-
|
1063 |
-
)
|
1064 |
-
|
1065 |
-
|
1066 |
-
|
1067 |
-
|
1068 |
-
|
1069 |
-
)
|
|
|
1070 |
if summary:
|
|
|
1071 |
return summary
|
1072 |
-
|
1073 |
-
f"[Summary
|
1074 |
-
|
1075 |
-
|
|
|
1076 |
|
1077 |
-
# Attempt
|
1078 |
if _openrouter_fallback_enabled:
|
|
|
1079 |
summary, errors["DeepSeekV3"] = await _call_openrouter(text, summary_type)
|
1080 |
if summary:
|
|
|
1081 |
return summary
|
1082 |
-
|
1083 |
-
f"[Summary
|
1084 |
-
f"{errors['DeepSeekV3']}; trying Llama 4 Scout."
|
1085 |
-
)
|
1086 |
else:
|
|
|
1087 |
errors["DeepSeekV3"] = "Service disabled/unavailable."
|
1088 |
|
1089 |
-
#
|
1090 |
-
|
1091 |
-
|
1092 |
-
|
1093 |
-
return summary
|
1094 |
-
logger.error(
|
1095 |
-
f"[Summary Generation] Llama 4 Scout failed: {errors['Llama4Scout']}."
|
1096 |
-
)
|
1097 |
-
else:
|
1098 |
-
errors["Llama4Scout"] = "Service disabled/unavailable."
|
1099 |
-
|
1100 |
-
# All failed
|
1101 |
-
error_details = "\n".join(f"- {k}: {v}" for k, v in errors.items() if v)
|
1102 |
-
return (
|
1103 |
-
"Sorry, I could not generate a summary after trying all available AI models.\n"
|
1104 |
-
f"Details:\n{error_details}"
|
1105 |
-
)
|
1106 |
-
|
1107 |
|
1108 |
|
1109 |
# --- Main Processing Logic ---
|
@@ -1268,7 +1216,7 @@ async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> No
|
|
1268 |
"2. I'll ask how you want it summarised (paragraph or points).\n"
|
1269 |
"3. Click the button for your choice.\n"
|
1270 |
"4. Wait while I fetch the content and generate the summary!\n\n"
|
1271 |
-
"⚙️ I try multiple methods to get content, especially for tricky websites or YouTube videos without standard transcripts. I use a
|
1272 |
"**Commands:**\n"
|
1273 |
"`/start` - Display the welcome message\n"
|
1274 |
"`/help` - Show this help message" )
|
@@ -1432,7 +1380,7 @@ async def lifespan(app: Starlette):
|
|
1432 |
|
1433 |
async def health_check(request: Request) -> PlainTextResponse:
|
1434 |
"""Simple health check endpoint."""
|
1435 |
-
global GROQ_LLAMA4_MODEL, GEMINI_PRO_EXP_MODEL, GEMINI_FLASH_MODEL,
|
1436 |
global APIFY_ACTOR_ID, APIFY_STRUCTURED_YT_ACTOR_ID # Add new ID here
|
1437 |
global _groq_enabled, _gemini_api_enabled, _openrouter_fallback_enabled
|
1438 |
global _apify_token_exists, _urltotext_key_exists, _rapidapi_key_exists, SUPADATA_API_KEY
|
@@ -1457,18 +1405,11 @@ async def health_check(request: Request) -> PlainTextResponse:
|
|
1457 |
return PlainTextResponse(
|
1458 |
f"TG Bot Summariser - Status: {bot_status} ({bot_username})\n"
|
1459 |
f"---\n"
|
1460 |
-
f"
|
1461 |
-
f"1.
|
1462 |
-
f"{
|
1463 |
-
f"
|
1464 |
-
f"{
|
1465 |
-
f"3. Gemini Flash 2.0: "
|
1466 |
-
f"{GEMINI_FLASH_MODEL if _gemini_api_enabled else 'DISABLED'}\n"
|
1467 |
-
f"4. OpenRouter DeepSeek V3: "
|
1468 |
-
f"{OPENROUTER_DEEPSEEK_MODEL if _openrouter_fallback_enabled else 'DISABLED'}\n"
|
1469 |
-
f"5. Groq Llama 4 Scout: "
|
1470 |
-
f"{GROQ_LLAMA4_MODEL if _groq_enabled else 'DISABLED'}\n"
|
1471 |
-
|
1472 |
f"---\n"
|
1473 |
f"Content Fetching Status:\n"
|
1474 |
# --- Updated YT Fallback List ---
|
|
|
99 |
RAPIDAPI_KEY = get_secret('RAPIDAPI_KEY')
|
100 |
WEBHOOK_SECRET = get_secret('WEBHOOK_SECRET')
|
101 |
|
102 |
+
# --- Model Configurations (Specific April 2025) ---
|
103 |
+
# Model Priority:
|
104 |
+
# 1. Groq Llama 4 Scout
|
105 |
+
# 2. Gemini 2.5 Pro Exp
|
106 |
+
# 3. Gemini 2.0 Flash
|
107 |
+
# 4. OpenRouter DeepSeek V3 Free
|
108 |
+
GROQ_LLAMA4_MODEL = os.environ.get("GROQ_LLAMA4_MODEL", "meta-llama/llama-4-scout-17b-16e-instruct") # Specific Llama 4 model
|
109 |
+
GEMINI_PRO_EXP_MODEL = os.environ.get("GEMINI_PRO_EXP_MODEL", "gemini-2.5-pro-exp-03-25")
|
110 |
+
GEMINI_FLASH_MODEL = os.environ.get("GEMINI_FLASH_MODEL", "gemini-2.0-flash-001")
|
111 |
+
OPENROUTER_DEEPSEEK_MODEL = os.environ.get("OPENROUTER_DEEPSEEK_MODEL", "deepseek/deepseek-chat-v3-0324:free") # Specific DeepSeek model
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
112 |
|
113 |
APIFY_ACTOR_ID = os.environ.get("APIFY_ACTOR_ID", "1s7eXiaukVuOr4Ueg") # YT Default
|
114 |
APIFY_STRUCTURED_YT_ACTOR_ID = "gpjTCWkGZS1lHc9pR" # YT Fallback 1 (New Structured Extractor)
|
|
|
144 |
if not WEBHOOK_SECRET: logger.info("Optional secret 'WEBHOOK_SECRET' not found. Webhook security disabled.")
|
145 |
|
146 |
logger.info("Secret loading and configuration check finished.")
|
147 |
+
logger.info(f"Summarizer 1 (Groq): {GROQ_LLAMA4_MODEL if _groq_enabled else 'DISABLED'}")
|
148 |
+
logger.info(f"Summarizer 2 (Gemini Pro Exp): {GEMINI_PRO_EXP_MODEL if _gemini_api_enabled else 'DISABLED'}")
|
149 |
+
logger.info(f"Summarizer 3 (Gemini Flash): {GEMINI_FLASH_MODEL if _gemini_api_enabled else 'DISABLED'}")
|
150 |
+
logger.info(f"Summarizer 4 (OpenRouter): {OPENROUTER_DEEPSEEK_MODEL if _openrouter_fallback_enabled else 'DISABLED'}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
151 |
# --- Updated Logging ---
|
152 |
logger.info(f"Using Apify Actor (YT Fallback 1 - Structured): {APIFY_STRUCTURED_YT_ACTOR_ID}")
|
153 |
logger.info(f"Using Apify Actor (YT Fallback 3 - Default): {APIFY_ACTOR_ID}") # Now fallback 3
|
|
|
852 |
# FIX: Define safety_settings explicitly with the 5 categories from the error log
|
853 |
# Removed the problematic loop that dynamically added potentially unsupported categories.
|
854 |
try:
|
855 |
+
safety_settings = {
|
856 |
+
HarmCategory.HARM_CATEGORY_HARASSMENT: HarmBlockThreshold.BLOCK_NONE,
|
857 |
+
HarmCategory.HARM_CATEGORY_HATE_SPEECH: HarmBlockThreshold.BLOCK_NONE,
|
858 |
+
HarmCategory.HARM_CATEGORY_SEXUALLY_EXPLICIT: HarmBlockThreshold.BLOCK_NONE,
|
859 |
+
HarmCategory.HARM_CATEGORY_DANGEROUS_CONTENT: HarmBlockThreshold.BLOCK_NONE,
|
860 |
+
HarmCategory.HARM_CATEGORY_CIVIC_INTEGRITY: HarmBlockThreshold.BLOCK_NONE, # Added based on error msg
|
861 |
+
}
|
862 |
+
logger.debug(f"[Gemini {model_name}] Using safety settings: { {k.name: v.name for k, v in safety_settings.items()} }")
|
|
|
|
|
|
|
|
|
|
|
863 |
except AttributeError:
|
864 |
+
# Handle case where HarmCategory or HarmBlockThreshold might not be loaded correctly
|
865 |
+
# This shouldn't happen if _gemini_sdk_available is True, but defensive coding
|
866 |
+
logger.error(f"[Gemini {model_name}] Failed to define safety settings. HarmCategory/HarmBlockThreshold missing?")
|
867 |
+
return None, f"Sorry, an internal error occurred configuring the AI service ({model_name})."
|
|
|
|
|
|
|
868 |
|
869 |
try:
|
870 |
logger.debug(f"[Gemini {model_name}] Initializing model {model_name}")
|
|
|
976 |
|
977 |
async def generate_summary(text: str, summary_type: str) -> str:
|
978 |
"""
|
979 |
+
Generates summary using the specific model hierarchy (April 2025):
|
980 |
+
1. Groq (Llama 4 Scout)
|
981 |
+
2. Gemini (2.5 Pro Exp)
|
982 |
+
3. Gemini (2.0 Flash)
|
983 |
+
4. OpenRouter (DeepSeek V3 Free)
|
984 |
+
Returns the summary text or a comprehensive error message.
|
985 |
"""
|
986 |
+
global _groq_enabled, _gemini_api_enabled, _openrouter_fallback_enabled
|
987 |
+
global GROQ_LLAMA4_MODEL, GEMINI_PRO_EXP_MODEL, GEMINI_FLASH_MODEL, OPENROUTER_DEEPSEEK_MODEL
|
988 |
+
|
989 |
+
logger.info("[Summary Generation] Starting process with specific April 2025 model hierarchy.")
|
990 |
summary: Optional[str] = None
|
991 |
errors: Dict[str, Optional[str]] = {
|
992 |
+
"Llama4Scout": None,
|
993 |
"GeminiProExp": None,
|
994 |
"GeminiFlash": None,
|
995 |
"DeepSeekV3": None,
|
|
|
996 |
}
|
997 |
|
998 |
+
# --- Attempt 1: Groq (Llama 4 Scout) ---
|
999 |
+
if _groq_enabled:
|
1000 |
+
logger.info(f"[Summary Generation] Attempting 1: Groq ({GROQ_LLAMA4_MODEL})")
|
1001 |
+
summary, errors["Llama4Scout"] = await _call_groq(text, summary_type)
|
|
|
1002 |
if summary:
|
1003 |
+
logger.info(f"[Summary Generation] Success with Groq ({GROQ_LLAMA4_MODEL}).")
|
1004 |
return summary
|
1005 |
+
else:
|
1006 |
+
logger.warning(f"[Summary Generation] Groq Llama 4 Scout failed. Error: {errors['Llama4Scout']}. Proceeding to Gemini 2.5 Pro Exp.")
|
|
|
|
|
1007 |
else:
|
1008 |
+
logger.warning("[Summary Generation] Groq (Llama 4 Scout) is disabled or unavailable. Skipping.")
|
1009 |
+
errors["Llama4Scout"] = "Service disabled/unavailable."
|
1010 |
|
1011 |
+
# --- Attempt 2: Gemini 2.5 Pro Exp ---
|
1012 |
if _gemini_api_enabled:
|
1013 |
+
logger.info(f"[Summary Generation] Attempting 2: Gemini ({GEMINI_PRO_EXP_MODEL})")
|
1014 |
+
summary, errors["GeminiProExp"] = await _call_gemini(text, summary_type, GEMINI_PRO_EXP_MODEL)
|
|
|
1015 |
if summary:
|
1016 |
+
logger.info(f"[Summary Generation] Success with Gemini ({GEMINI_PRO_EXP_MODEL}).")
|
1017 |
return summary
|
1018 |
+
else:
|
1019 |
+
logger.warning(f"[Summary Generation] Gemini 2.5 Pro Exp failed. Error: {errors['GeminiProExp']}. Proceeding to Gemini 2.0 Flash.")
|
1020 |
+
else:
|
1021 |
+
logger.warning("[Summary Generation] Gemini API is disabled or unavailable. Skipping Gemini 2.5 Pro Exp & 2.0 Flash.")
|
1022 |
+
errors["GeminiProExp"] = "Service disabled/unavailable."
|
1023 |
+
errors["GeminiFlash"] = "Service disabled/unavailable."
|
1024 |
+
|
1025 |
+
# --- Attempt 3: Gemini 2.0 Flash ---
|
1026 |
+
if _gemini_api_enabled and errors["GeminiFlash"] is None: # Check if Gemini API is enabled AND wasn't already marked as failed/skipped
|
1027 |
+
logger.info(f"[Summary Generation] Attempting 3: Gemini ({GEMINI_FLASH_MODEL})")
|
1028 |
+
summary, errors["GeminiFlash"] = await _call_gemini(text, summary_type, GEMINI_FLASH_MODEL)
|
1029 |
if summary:
|
1030 |
+
logger.info(f"[Summary Generation] Success with Gemini ({GEMINI_FLASH_MODEL}).")
|
1031 |
return summary
|
1032 |
+
else:
|
1033 |
+
logger.warning(f"[Summary Generation] Gemini 2.0 Flash failed. Error: {errors['GeminiFlash']}. Proceeding to OpenRouter DeepSeek V3.")
|
1034 |
+
elif errors["GeminiFlash"] is None: # Only log skip message if it wasn't already marked as disabled
|
1035 |
+
logger.warning("[Summary Generation] Skipping Gemini 2.0 Flash (API was disabled).")
|
1036 |
+
errors["GeminiFlash"] = "Service disabled/unavailable."
|
1037 |
|
1038 |
+
# --- Attempt 4: OpenRouter (DeepSeek V3 Free - Final Fallback) ---
|
1039 |
if _openrouter_fallback_enabled:
|
1040 |
+
logger.info(f"[Summary Generation] Attempting 4: OpenRouter ({OPENROUTER_DEEPSEEK_MODEL})")
|
1041 |
summary, errors["DeepSeekV3"] = await _call_openrouter(text, summary_type)
|
1042 |
if summary:
|
1043 |
+
logger.info(f"[Summary Generation] Success with OpenRouter ({OPENROUTER_DEEPSEEK_MODEL}).")
|
1044 |
return summary
|
1045 |
+
else:
|
1046 |
+
logger.error(f"[Summary Generation] OpenRouter DeepSeek V3 (Final Fallback) also failed. Error: {errors['DeepSeekV3']}")
|
|
|
|
|
1047 |
else:
|
1048 |
+
logger.error("[Summary Generation] OpenRouter fallback (DeepSeek V3) is disabled or unavailable. Cannot proceed.")
|
1049 |
errors["DeepSeekV3"] = "Service disabled/unavailable."
|
1050 |
|
1051 |
+
# --- All Attempts Failed ---
|
1052 |
+
logger.error("[Summary Generation] All summarization models failed.")
|
1053 |
+
error_details = "\n".join([f"- {model}: {err}" for model, err in errors.items() if err])
|
1054 |
+
return f"Sorry, I couldn't generate a summary after trying all available AI models.\nDetails:\n{error_details}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1055 |
|
1056 |
|
1057 |
# --- Main Processing Logic ---
|
|
|
1216 |
"2. I'll ask how you want it summarised (paragraph or points).\n"
|
1217 |
"3. Click the button for your choice.\n"
|
1218 |
"4. Wait while I fetch the content and generate the summary!\n\n"
|
1219 |
+
"⚙️ I try multiple methods to get content, especially for tricky websites or YouTube videos without standard transcripts. I then use a sequence of AI models (Llama 4 Scout, Gemini 2.5 Pro, Gemini 2.0 Flash, DeepSeek V3) to summarise.\n\n" # Updated help text
|
1220 |
"**Commands:**\n"
|
1221 |
"`/start` - Display the welcome message\n"
|
1222 |
"`/help` - Show this help message" )
|
|
|
1380 |
|
1381 |
async def health_check(request: Request) -> PlainTextResponse:
|
1382 |
"""Simple health check endpoint."""
|
1383 |
+
global GROQ_LLAMA4_MODEL, GEMINI_PRO_EXP_MODEL, GEMINI_FLASH_MODEL, OPENROUTER_DEEPSEEK_MODEL
|
1384 |
global APIFY_ACTOR_ID, APIFY_STRUCTURED_YT_ACTOR_ID # Add new ID here
|
1385 |
global _groq_enabled, _gemini_api_enabled, _openrouter_fallback_enabled
|
1386 |
global _apify_token_exists, _urltotext_key_exists, _rapidapi_key_exists, SUPADATA_API_KEY
|
|
|
1405 |
return PlainTextResponse(
|
1406 |
f"TG Bot Summariser - Status: {bot_status} ({bot_username})\n"
|
1407 |
f"---\n"
|
1408 |
+
f"Summarizer Priority (April 2025 - Specific):\n"
|
1409 |
+
f"1. Groq API: {GROQ_LLAMA4_MODEL if _groq_enabled else 'DISABLED'}\n"
|
1410 |
+
f"2. Gemini API: {GEMINI_PRO_EXP_MODEL if _gemini_api_enabled else 'DISABLED'}\n"
|
1411 |
+
f"3. Gemini API: {GEMINI_FLASH_MODEL if _gemini_api_enabled else 'DISABLED'}\n"
|
1412 |
+
f"4. OpenRouter API: {OPENROUTER_DEEPSEEK_MODEL if _openrouter_fallback_enabled else 'DISABLED'}\n"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1413 |
f"---\n"
|
1414 |
f"Content Fetching Status:\n"
|
1415 |
# --- Updated YT Fallback List ---
|