Spaces:
Build error
Build error
CB commited on
Update streamlit_app.py
Browse files- streamlit_app.py +195 -40
streamlit_app.py
CHANGED
|
@@ -5,6 +5,8 @@ import string
|
|
| 5 |
import hashlib
|
| 6 |
import traceback
|
| 7 |
import inspect
|
|
|
|
|
|
|
| 8 |
from glob import glob
|
| 9 |
from pathlib import Path
|
| 10 |
from difflib import SequenceMatcher
|
|
@@ -57,6 +59,8 @@ st.session_state.setdefault("api_key", os.getenv("GOOGLE_API_KEY", ""))
|
|
| 57 |
st.session_state.setdefault("last_model", "")
|
| 58 |
st.session_state.setdefault("last_url_value", "")
|
| 59 |
|
|
|
|
|
|
|
| 60 |
def sanitize_filename(path_str: str):
|
| 61 |
return Path(path_str).name.lower().translate(str.maketrans("", "", string.punctuation)).replace(" ", "_")
|
| 62 |
|
|
@@ -142,16 +146,24 @@ def clear_all_video_state():
|
|
| 142 |
except Exception:
|
| 143 |
pass
|
| 144 |
|
| 145 |
-
#
|
| 146 |
def expand_url(short_url, timeout=10):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 147 |
try:
|
| 148 |
-
r = requests.get(short_url, allow_redirects=True, timeout=timeout, headers=
|
| 149 |
-
|
| 150 |
-
|
|
|
|
| 151 |
except Exception as e:
|
| 152 |
return None, f"error: {e}"
|
| 153 |
|
| 154 |
def extract_video_from_html(html, base_url=None):
|
|
|
|
|
|
|
|
|
|
| 155 |
soup = BeautifulSoup(html, "html.parser")
|
| 156 |
og = soup.find("meta", property="og:video")
|
| 157 |
if og and og.get("content"):
|
|
@@ -166,7 +178,6 @@ def extract_video_from_html(html, base_url=None):
|
|
| 166 |
return source.get("src")
|
| 167 |
for script in soup.find_all("script", type="application/ld+json"):
|
| 168 |
try:
|
| 169 |
-
import json
|
| 170 |
data = json.loads(script.string or "{}")
|
| 171 |
if isinstance(data, dict):
|
| 172 |
video = data.get("video") or data.get("videoObject") or data.get("mainEntity")
|
|
@@ -188,6 +199,117 @@ def extract_video_from_html(html, base_url=None):
|
|
| 188 |
return href
|
| 189 |
return None
|
| 190 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 191 |
def upload_video_sdk(filepath: str):
|
| 192 |
key = get_effective_api_key()
|
| 193 |
if not key:
|
|
@@ -240,40 +362,34 @@ def remove_prompt_echo(prompt: str, text: str, check_len: int = 600, ratio_thres
|
|
| 240 |
return b_full[len(ph):].lstrip(" \n:-")
|
| 241 |
return text
|
| 242 |
|
| 243 |
-
# Helper: try to call GenerativeModel with compatible signature
|
| 244 |
def generative_model_call_flexible(model_name, messages, files=None, max_output_tokens=1024):
|
| 245 |
"""
|
| 246 |
Try different call patterns for genai.GenerativeModel depending on its constructor/signature.
|
| 247 |
-
|
| 248 |
"""
|
| 249 |
if not HAS_GENAI or genai is None:
|
| 250 |
raise RuntimeError("genai not available")
|
| 251 |
|
| 252 |
-
# Inspect GenerativeModel if present
|
| 253 |
GM = getattr(genai, "GenerativeModel", None)
|
| 254 |
if GM is None:
|
| 255 |
raise RuntimeError("GenerativeModel not available")
|
| 256 |
|
| 257 |
-
#
|
| 258 |
try:
|
| 259 |
sig = inspect.signature(GM)
|
| 260 |
params = sig.parameters
|
| 261 |
-
# prefer 'model' if available
|
| 262 |
if "model" in params:
|
| 263 |
gm = GM(model=model_name)
|
| 264 |
elif "model_name" in params:
|
| 265 |
gm = GM(model_name=model_name)
|
| 266 |
else:
|
| 267 |
-
# fallback to no-arg constructor
|
| 268 |
gm = GM()
|
| 269 |
-
# attempt to set attribute if accepted
|
| 270 |
try:
|
| 271 |
if hasattr(gm, "model"):
|
| 272 |
setattr(gm, "model", model_name)
|
| 273 |
except Exception:
|
| 274 |
pass
|
| 275 |
except Exception:
|
| 276 |
-
# if signature inspection fails, try common constructors defensively
|
| 277 |
try:
|
| 278 |
gm = GM(model=model_name)
|
| 279 |
except TypeError:
|
|
@@ -282,28 +398,40 @@ def generative_model_call_flexible(model_name, messages, files=None, max_output_
|
|
| 282 |
except TypeError:
|
| 283 |
gm = GM()
|
| 284 |
|
| 285 |
-
# Now
|
|
|
|
| 286 |
if hasattr(gm, "generate_content"):
|
| 287 |
-
return gm.generate_content(messages, files=files, max_output_tokens=max_output_tokens)
|
| 288 |
-
if hasattr(gm, "generate"):
|
| 289 |
-
# some versions use generate(messages,...)
|
| 290 |
try:
|
| 291 |
-
return gm.
|
| 292 |
except TypeError:
|
| 293 |
-
# try positional
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 294 |
return gm.generate(messages, max_output_tokens=max_output_tokens)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
raise RuntimeError("No usable generate method on GenerativeModel instance")
|
| 296 |
|
| 297 |
-
# Fallback HTTP call using the REST Responses endpoint if the SDK is present but broken.
|
| 298 |
-
# This requires an API key and uses the public Responses API endpoint.
|
| 299 |
def responses_http_call(api_key, model, messages, file_name=None, max_output_tokens=1024, safety_settings=None):
|
| 300 |
"""
|
| 301 |
-
|
| 302 |
-
|
| 303 |
"""
|
| 304 |
-
#
|
| 305 |
-
url = "https://generativeai.googleapis.com/
|
| 306 |
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
|
|
|
| 307 |
payload = {
|
| 308 |
"messages": [{"role": m.get("role", "user"), "content": [{"type": "text", "text": m.get("content", "")}]} for m in messages],
|
| 309 |
"maxOutputTokens": max_output_tokens,
|
|
@@ -311,7 +439,7 @@ def responses_http_call(api_key, model, messages, file_name=None, max_output_tok
|
|
| 311 |
if safety_settings:
|
| 312 |
payload["safetySettings"] = safety_settings
|
| 313 |
if file_name:
|
| 314 |
-
#
|
| 315 |
payload["files"] = [{"name": file_name}]
|
| 316 |
try:
|
| 317 |
r = requests.post(url, json=payload, headers=headers, timeout=60)
|
|
@@ -365,15 +493,27 @@ if st.sidebar.button("Load Video", use_container_width=True):
|
|
| 365 |
url_val = st.session_state.get("url", "").strip()
|
| 366 |
final_url = url_val
|
| 367 |
html_text = None
|
|
|
|
| 368 |
if url_val:
|
| 369 |
-
|
| 370 |
-
if
|
| 371 |
-
|
| 372 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 373 |
else:
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
|
|
|
|
|
|
|
|
|
| 377 |
extracted = extract_video_from_html(html_text, base_url=final_url)
|
| 378 |
target_url_for_ytdlp = extracted or final_url
|
| 379 |
path = download_video_ytdlp(target_url_for_ytdlp, str(DATA_DIR), vpw)
|
|
@@ -388,6 +528,7 @@ if st.sidebar.button("Load Video", use_container_width=True):
|
|
| 388 |
except Exception as e:
|
| 389 |
st.sidebar.error(f"Failed to load video: {e}")
|
| 390 |
|
|
|
|
| 391 |
if st.session_state["videos"]:
|
| 392 |
try:
|
| 393 |
st.sidebar.video(st.session_state["videos"], loop=st.session_state.get("loop_video", False))
|
|
@@ -499,7 +640,7 @@ if generate_now and not st.session_state.get("busy"):
|
|
| 499 |
response = None
|
| 500 |
diagnostics = {"attempts": []}
|
| 501 |
|
| 502 |
-
# Attempt #1: genai.responses.generate (modern)
|
| 503 |
try:
|
| 504 |
if hasattr(genai, "responses") and hasattr(genai.responses, "generate"):
|
| 505 |
diagnostics["attempts"].append("responses.generate")
|
|
@@ -519,6 +660,7 @@ if generate_now and not st.session_state.get("busy"):
|
|
| 519 |
try:
|
| 520 |
if hasattr(genai, "GenerativeModel"):
|
| 521 |
diagnostics["attempts"].append("GenerativeModel")
|
|
|
|
| 522 |
response = generative_model_call_flexible(model_used, [system_msg, user_msg], files=[{"name": fname}], max_output_tokens=max_tokens)
|
| 523 |
except Exception as e:
|
| 524 |
diagnostics["GenerativeModel_error"] = str(e)
|
|
@@ -537,7 +679,7 @@ if generate_now and not st.session_state.get("busy"):
|
|
| 537 |
diagnostics["top_level_error"] = str(e)
|
| 538 |
response = None
|
| 539 |
|
| 540 |
-
# Attempt #4: fallback HTTP Responses call
|
| 541 |
if response is None:
|
| 542 |
try:
|
| 543 |
diagnostics["attempts"].append("http_fallback")
|
|
@@ -562,10 +704,22 @@ if generate_now and not st.session_state.get("busy"):
|
|
| 562 |
outputs = list(val)
|
| 563 |
break
|
| 564 |
if not outputs:
|
| 565 |
-
|
| 566 |
-
|
| 567 |
-
|
| 568 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 569 |
else:
|
| 570 |
for attr in ("output", "candidates", "items", "responses"):
|
| 571 |
val = getattr(response, attr, None)
|
|
@@ -581,7 +735,7 @@ if generate_now and not st.session_state.get("busy"):
|
|
| 581 |
if not outputs:
|
| 582 |
candidate_text = None
|
| 583 |
if isinstance(response, dict):
|
| 584 |
-
candidate_text = response.get("text") or response.get("message")
|
| 585 |
else:
|
| 586 |
candidate_text = getattr(response, "text", None) or getattr(response, "message", None)
|
| 587 |
if candidate_text:
|
|
@@ -592,6 +746,7 @@ if generate_now and not st.session_state.get("busy"):
|
|
| 592 |
if not item:
|
| 593 |
continue
|
| 594 |
if isinstance(item, dict):
|
|
|
|
| 595 |
for k in ("content", "text", "message", "output_text", "output"):
|
| 596 |
v = item.get(k)
|
| 597 |
if v:
|
|
|
|
| 5 |
import hashlib
|
| 6 |
import traceback
|
| 7 |
import inspect
|
| 8 |
+
import re
|
| 9 |
+
import json
|
| 10 |
from glob import glob
|
| 11 |
from pathlib import Path
|
| 12 |
from difflib import SequenceMatcher
|
|
|
|
| 59 |
st.session_state.setdefault("last_model", "")
|
| 60 |
st.session_state.setdefault("last_url_value", "")
|
| 61 |
|
| 62 |
+
HEADERS = {"User-Agent": "Mozilla/5.0 (compatible)"}
|
| 63 |
+
|
| 64 |
def sanitize_filename(path_str: str):
|
| 65 |
return Path(path_str).name.lower().translate(str.maketrans("", "", string.punctuation)).replace(" ", "_")
|
| 66 |
|
|
|
|
| 146 |
except Exception:
|
| 147 |
pass
|
| 148 |
|
| 149 |
+
# --- Twitter (t.co / X) helpers integrated into expand/extract flow ---
|
| 150 |
def expand_url(short_url, timeout=10):
|
| 151 |
+
"""
|
| 152 |
+
General URL expander. For t.co/twitter shortlinks we try multiple variants
|
| 153 |
+
and return final URL and HTML if available.
|
| 154 |
+
"""
|
| 155 |
try:
|
| 156 |
+
r = requests.get(short_url, allow_redirects=True, timeout=timeout, headers=HEADERS)
|
| 157 |
+
r.raise_for_status()
|
| 158 |
+
final = r.url
|
| 159 |
+
return final, r.text
|
| 160 |
except Exception as e:
|
| 161 |
return None, f"error: {e}"
|
| 162 |
|
| 163 |
def extract_video_from_html(html, base_url=None):
|
| 164 |
+
"""
|
| 165 |
+
Generic extractor tries og:video, <video>, LD+JSON, twitter tags, and links to common hosts.
|
| 166 |
+
"""
|
| 167 |
soup = BeautifulSoup(html, "html.parser")
|
| 168 |
og = soup.find("meta", property="og:video")
|
| 169 |
if og and og.get("content"):
|
|
|
|
| 178 |
return source.get("src")
|
| 179 |
for script in soup.find_all("script", type="application/ld+json"):
|
| 180 |
try:
|
|
|
|
| 181 |
data = json.loads(script.string or "{}")
|
| 182 |
if isinstance(data, dict):
|
| 183 |
video = data.get("video") or data.get("videoObject") or data.get("mainEntity")
|
|
|
|
| 199 |
return href
|
| 200 |
return None
|
| 201 |
|
| 202 |
+
def extract_video_from_twitter_html(html):
|
| 203 |
+
"""
|
| 204 |
+
Attempt to pull direct MP4 URL from Twitter/X HTML by searching JSON blobs and OG tags.
|
| 205 |
+
This is a best-effort extractor and may fail if Twitter/X obfuscates content.
|
| 206 |
+
"""
|
| 207 |
+
soup = BeautifulSoup(html, "html.parser")
|
| 208 |
+
|
| 209 |
+
# 1) Open Graph video tag
|
| 210 |
+
og_video = soup.find("meta", property="og:video")
|
| 211 |
+
if og_video and og_video.get("content"):
|
| 212 |
+
return og_video["content"]
|
| 213 |
+
|
| 214 |
+
# 2) Look for JSON blobs in <script> tags and search for variants/urls
|
| 215 |
+
scripts = soup.find_all("script")
|
| 216 |
+
for s in scripts:
|
| 217 |
+
txt = s.string
|
| 218 |
+
if not txt:
|
| 219 |
+
continue
|
| 220 |
+
# crude detect for embedded JSON-ish blobs that include "video_info" or "variants"
|
| 221 |
+
if "video_info" in txt or "variants" in txt or "playbackUrl" in txt or "media" in txt:
|
| 222 |
+
# try to extract a JSON object within the script text
|
| 223 |
+
m = re.search(r"(?s)(\{.+\})", txt)
|
| 224 |
+
if not m:
|
| 225 |
+
continue
|
| 226 |
+
try:
|
| 227 |
+
blob = json.loads(m.group(1))
|
| 228 |
+
except Exception:
|
| 229 |
+
# sometimes it's not strict JSON; skip
|
| 230 |
+
continue
|
| 231 |
+
|
| 232 |
+
# deep search for urls and variants
|
| 233 |
+
def find_media_urls(obj):
|
| 234 |
+
if isinstance(obj, dict):
|
| 235 |
+
for k, v in obj.items():
|
| 236 |
+
if isinstance(v, str):
|
| 237 |
+
if v.startswith("https://") and v.endswith(".mp4"):
|
| 238 |
+
yield v
|
| 239 |
+
else:
|
| 240 |
+
yield from find_media_urls(v)
|
| 241 |
+
elif isinstance(obj, list):
|
| 242 |
+
for it in obj:
|
| 243 |
+
yield from find_media_urls(it)
|
| 244 |
+
|
| 245 |
+
for url in find_media_urls(blob):
|
| 246 |
+
return url
|
| 247 |
+
|
| 248 |
+
# also look for variant lists
|
| 249 |
+
def find_variants(obj):
|
| 250 |
+
if isinstance(obj, dict):
|
| 251 |
+
for k, v in obj.items():
|
| 252 |
+
if k == "variants" and isinstance(v, list):
|
| 253 |
+
for vi in v:
|
| 254 |
+
if isinstance(vi, dict):
|
| 255 |
+
url = vi.get("url") or vi.get("playbackUrl")
|
| 256 |
+
ct = vi.get("content_type", "") or vi.get("contentType", "")
|
| 257 |
+
if url and url.startswith("http") and ("mp4" in url or "video" in ct or "video" in url):
|
| 258 |
+
yield url
|
| 259 |
+
else:
|
| 260 |
+
yield from find_variants(v)
|
| 261 |
+
elif isinstance(obj, list):
|
| 262 |
+
for it in obj:
|
| 263 |
+
yield from find_variants(it)
|
| 264 |
+
|
| 265 |
+
for url in find_variants(blob):
|
| 266 |
+
return url
|
| 267 |
+
|
| 268 |
+
return None
|
| 269 |
+
|
| 270 |
+
def extract_direct_twitter_video(url):
|
| 271 |
+
"""
|
| 272 |
+
Expand t.co and try several page variants (mobile, amp, x.com) and oEmbed.
|
| 273 |
+
Returns (direct_video_url or None, info_string)
|
| 274 |
+
"""
|
| 275 |
+
final, html_or_err = expand_url(url)
|
| 276 |
+
if final is None:
|
| 277 |
+
return None, html_or_err
|
| 278 |
+
|
| 279 |
+
# Try several variants (mobile, x.com, with query params)
|
| 280 |
+
variants = [
|
| 281 |
+
final,
|
| 282 |
+
final.replace("://twitter.com/", "://mobile.twitter.com/"),
|
| 283 |
+
final.replace("://twitter.com/", "://x.com/"),
|
| 284 |
+
final + "?s=20",
|
| 285 |
+
final + "?ref_src=twsrc%5Etfw",
|
| 286 |
+
]
|
| 287 |
+
for u in variants:
|
| 288 |
+
try:
|
| 289 |
+
r = requests.get(u, allow_redirects=True, headers=HEADERS, timeout=10)
|
| 290 |
+
r.raise_for_status()
|
| 291 |
+
direct = extract_video_from_twitter_html(r.text)
|
| 292 |
+
if direct:
|
| 293 |
+
return direct, u
|
| 294 |
+
except Exception:
|
| 295 |
+
continue
|
| 296 |
+
|
| 297 |
+
# Try oEmbed as last resort
|
| 298 |
+
try:
|
| 299 |
+
oembed = requests.get("https://publish.twitter.com/oembed?url=" + final, headers=HEADERS, timeout=6)
|
| 300 |
+
if oembed.ok:
|
| 301 |
+
j = oembed.json()
|
| 302 |
+
html = j.get("html", "")
|
| 303 |
+
soup = BeautifulSoup(html, "html.parser")
|
| 304 |
+
video = soup.find("video")
|
| 305 |
+
if video and video.get("src"):
|
| 306 |
+
return video["src"], final
|
| 307 |
+
except Exception:
|
| 308 |
+
pass
|
| 309 |
+
|
| 310 |
+
return None, "not found"
|
| 311 |
+
|
| 312 |
+
# --- Upload helpers for Generative AI SDK + HTTP fallback (fixed endpoint/use patterns) ---
|
| 313 |
def upload_video_sdk(filepath: str):
|
| 314 |
key = get_effective_api_key()
|
| 315 |
if not key:
|
|
|
|
| 362 |
return b_full[len(ph):].lstrip(" \n:-")
|
| 363 |
return text
|
| 364 |
|
|
|
|
| 365 |
def generative_model_call_flexible(model_name, messages, files=None, max_output_tokens=1024):
|
| 366 |
"""
|
| 367 |
Try different call patterns for genai.GenerativeModel depending on its constructor/signature.
|
| 368 |
+
Do NOT pass unsupported keywords called 'files' into generate_content() if the SDK rejects them.
|
| 369 |
"""
|
| 370 |
if not HAS_GENAI or genai is None:
|
| 371 |
raise RuntimeError("genai not available")
|
| 372 |
|
|
|
|
| 373 |
GM = getattr(genai, "GenerativeModel", None)
|
| 374 |
if GM is None:
|
| 375 |
raise RuntimeError("GenerativeModel not available")
|
| 376 |
|
| 377 |
+
# Construct instance robustly
|
| 378 |
try:
|
| 379 |
sig = inspect.signature(GM)
|
| 380 |
params = sig.parameters
|
|
|
|
| 381 |
if "model" in params:
|
| 382 |
gm = GM(model=model_name)
|
| 383 |
elif "model_name" in params:
|
| 384 |
gm = GM(model_name=model_name)
|
| 385 |
else:
|
|
|
|
| 386 |
gm = GM()
|
|
|
|
| 387 |
try:
|
| 388 |
if hasattr(gm, "model"):
|
| 389 |
setattr(gm, "model", model_name)
|
| 390 |
except Exception:
|
| 391 |
pass
|
| 392 |
except Exception:
|
|
|
|
| 393 |
try:
|
| 394 |
gm = GM(model=model_name)
|
| 395 |
except TypeError:
|
|
|
|
| 398 |
except TypeError:
|
| 399 |
gm = GM()
|
| 400 |
|
| 401 |
+
# Now attempt supported generate methods but avoid unsupported kwargs
|
| 402 |
+
# 1) generate_content(messages...) may accept just messages and options (no files)
|
| 403 |
if hasattr(gm, "generate_content"):
|
|
|
|
|
|
|
|
|
|
| 404 |
try:
|
| 405 |
+
return gm.generate_content(messages, max_output_tokens=max_output_tokens)
|
| 406 |
except TypeError:
|
| 407 |
+
# generate_content signature doesn't accept our args; try positional single string fallback
|
| 408 |
+
try:
|
| 409 |
+
# some versions expect a string prompt
|
| 410 |
+
prompt = messages[-1].get("content") if isinstance(messages, (list, tuple)) and messages else str(messages)
|
| 411 |
+
return gm.generate_content(prompt)
|
| 412 |
+
except Exception as e:
|
| 413 |
+
raise RuntimeError(f"GenerativeModel.generate_content unusable: {e}")
|
| 414 |
+
# 2) generate(...) variants
|
| 415 |
+
if hasattr(gm, "generate"):
|
| 416 |
+
try:
|
| 417 |
return gm.generate(messages, max_output_tokens=max_output_tokens)
|
| 418 |
+
except TypeError:
|
| 419 |
+
try:
|
| 420 |
+
return gm.generate(messages)
|
| 421 |
+
except Exception as e:
|
| 422 |
+
raise RuntimeError(f"GenerativeModel.generate unusable: {e}")
|
| 423 |
+
|
| 424 |
raise RuntimeError("No usable generate method on GenerativeModel instance")
|
| 425 |
|
|
|
|
|
|
|
| 426 |
def responses_http_call(api_key, model, messages, file_name=None, max_output_tokens=1024, safety_settings=None):
|
| 427 |
"""
|
| 428 |
+
Fallback to the public Responses API v1 endpoint (modern). Construct a minimal request body.
|
| 429 |
+
Note: endpoint and schema may change; this uses a simple v1-compatible payload.
|
| 430 |
"""
|
| 431 |
+
# Use the modern Responses v1 endpoint format
|
| 432 |
+
url = "https://api.generativeai.googleapis.com/v1/models/{model}:generateMessage".format(model=model)
|
| 433 |
headers = {"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}
|
| 434 |
+
# Build minimal 'messages' style payload expected by many GenAI endpoints
|
| 435 |
payload = {
|
| 436 |
"messages": [{"role": m.get("role", "user"), "content": [{"type": "text", "text": m.get("content", "")}]} for m in messages],
|
| 437 |
"maxOutputTokens": max_output_tokens,
|
|
|
|
| 439 |
if safety_settings:
|
| 440 |
payload["safetySettings"] = safety_settings
|
| 441 |
if file_name:
|
| 442 |
+
# Some endpoints accept files as references
|
| 443 |
payload["files"] = [{"name": file_name}]
|
| 444 |
try:
|
| 445 |
r = requests.post(url, json=payload, headers=headers, timeout=60)
|
|
|
|
| 493 |
url_val = st.session_state.get("url", "").strip()
|
| 494 |
final_url = url_val
|
| 495 |
html_text = None
|
| 496 |
+
extracted = None
|
| 497 |
if url_val:
|
| 498 |
+
# Special handling for t.co / twitter shortlinks
|
| 499 |
+
if "t.co/" in url_val or ("twitter.com" in url_val or "x.com" in url_val):
|
| 500 |
+
extracted, src_info = extract_direct_twitter_video(url_val)
|
| 501 |
+
if extracted:
|
| 502 |
+
final_url = extracted
|
| 503 |
+
html_text = None
|
| 504 |
+
else:
|
| 505 |
+
# fallback to expand_url to get final page HTML
|
| 506 |
+
expanded, html_or_err = expand_url(url_val)
|
| 507 |
+
if expanded:
|
| 508 |
+
final_url = expanded
|
| 509 |
+
html_text = html_or_err
|
| 510 |
else:
|
| 511 |
+
expanded, html_or_err = expand_url(url_val)
|
| 512 |
+
if expanded:
|
| 513 |
+
final_url = expanded
|
| 514 |
+
html_text = html_or_err
|
| 515 |
+
|
| 516 |
+
if html_text and not extracted:
|
| 517 |
extracted = extract_video_from_html(html_text, base_url=final_url)
|
| 518 |
target_url_for_ytdlp = extracted or final_url
|
| 519 |
path = download_video_ytdlp(target_url_for_ytdlp, str(DATA_DIR), vpw)
|
|
|
|
| 528 |
except Exception as e:
|
| 529 |
st.sidebar.error(f"Failed to load video: {e}")
|
| 530 |
|
| 531 |
+
# Player / sidebar controls
|
| 532 |
if st.session_state["videos"]:
|
| 533 |
try:
|
| 534 |
st.sidebar.video(st.session_state["videos"], loop=st.session_state.get("loop_video", False))
|
|
|
|
| 640 |
response = None
|
| 641 |
diagnostics = {"attempts": []}
|
| 642 |
|
| 643 |
+
# Attempt #1: genai.responses.generate (modern public SDK)
|
| 644 |
try:
|
| 645 |
if hasattr(genai, "responses") and hasattr(genai.responses, "generate"):
|
| 646 |
diagnostics["attempts"].append("responses.generate")
|
|
|
|
| 660 |
try:
|
| 661 |
if hasattr(genai, "GenerativeModel"):
|
| 662 |
diagnostics["attempts"].append("GenerativeModel")
|
| 663 |
+
# generative_model_call_flexible avoids passing unsupported 'files' kwarg
|
| 664 |
response = generative_model_call_flexible(model_used, [system_msg, user_msg], files=[{"name": fname}], max_output_tokens=max_tokens)
|
| 665 |
except Exception as e:
|
| 666 |
diagnostics["GenerativeModel_error"] = str(e)
|
|
|
|
| 679 |
diagnostics["top_level_error"] = str(e)
|
| 680 |
response = None
|
| 681 |
|
| 682 |
+
# Attempt #4: fallback HTTP Responses call (modern endpoint)
|
| 683 |
if response is None:
|
| 684 |
try:
|
| 685 |
diagnostics["attempts"].append("http_fallback")
|
|
|
|
| 704 |
outputs = list(val)
|
| 705 |
break
|
| 706 |
if not outputs:
|
| 707 |
+
# some Responses v1 return {'message': {...}}
|
| 708 |
+
msg = response.get("message") or response.get("response") or response.get("output")
|
| 709 |
+
if isinstance(msg, dict):
|
| 710 |
+
# try to extract text from structured message
|
| 711 |
+
c = msg.get("content")
|
| 712 |
+
if isinstance(c, list):
|
| 713 |
+
for part in c:
|
| 714 |
+
if isinstance(part, dict) and part.get("type") == "output_text":
|
| 715 |
+
outputs.append({"text": part.get("text")})
|
| 716 |
+
elif isinstance(part, dict) and part.get("type") == "text":
|
| 717 |
+
outputs.append({"text": part.get("text")})
|
| 718 |
+
else:
|
| 719 |
+
# fallback: join string values
|
| 720 |
+
for v in response.values():
|
| 721 |
+
if isinstance(v, str) and v.strip():
|
| 722 |
+
outputs.append({"text": v.strip()})
|
| 723 |
else:
|
| 724 |
for attr in ("output", "candidates", "items", "responses"):
|
| 725 |
val = getattr(response, attr, None)
|
|
|
|
| 735 |
if not outputs:
|
| 736 |
candidate_text = None
|
| 737 |
if isinstance(response, dict):
|
| 738 |
+
candidate_text = response.get("text") or response.get("message") or response.get("output_text")
|
| 739 |
else:
|
| 740 |
candidate_text = getattr(response, "text", None) or getattr(response, "message", None)
|
| 741 |
if candidate_text:
|
|
|
|
| 746 |
if not item:
|
| 747 |
continue
|
| 748 |
if isinstance(item, dict):
|
| 749 |
+
# common dict shapes
|
| 750 |
for k in ("content", "text", "message", "output_text", "output"):
|
| 751 |
v = item.get(k)
|
| 752 |
if v:
|