Spaces:
Sleeping
Sleeping
CB commited on
Update streamlit_app.py
Browse files- streamlit_app.py +224 -137
streamlit_app.py
CHANGED
|
@@ -8,6 +8,9 @@ from glob import glob
|
|
| 8 |
from pathlib import Path
|
| 9 |
from difflib import SequenceMatcher
|
| 10 |
|
|
|
|
|
|
|
|
|
|
| 11 |
import yt_dlp
|
| 12 |
import ffmpeg
|
| 13 |
import streamlit as st
|
|
@@ -15,30 +18,25 @@ from dotenv import load_dotenv
|
|
| 15 |
|
| 16 |
load_dotenv()
|
| 17 |
|
| 18 |
-
# phi agent
|
| 19 |
-
try:
|
| 20 |
-
from phi.agent import Agent # noqa: F401
|
| 21 |
-
from phi.model.google import Gemini # noqa: F401
|
| 22 |
-
from phi.tools.duckduckgo import DuckDuckGo # noqa: F401
|
| 23 |
-
HAS_PHI = True
|
| 24 |
-
except Exception:
|
| 25 |
-
HAS_PHI = False
|
| 26 |
-
|
| 27 |
HAS_PHI = False
|
| 28 |
|
|
|
|
| 29 |
try:
|
| 30 |
-
import google.generativeai as genai
|
| 31 |
from google.generativeai import upload_file, get_file # type: ignore
|
| 32 |
HAS_GENAI = True
|
| 33 |
except Exception:
|
| 34 |
genai = None
|
| 35 |
-
upload_file =
|
|
|
|
| 36 |
HAS_GENAI = False
|
| 37 |
|
| 38 |
st.set_page_config(page_title="Generate the story of videos", layout="wide")
|
| 39 |
DATA_DIR = Path("./data")
|
| 40 |
DATA_DIR.mkdir(exist_ok=True)
|
| 41 |
|
|
|
|
| 42 |
st.session_state.setdefault("videos", "")
|
| 43 |
st.session_state.setdefault("loop_video", False)
|
| 44 |
st.session_state.setdefault("uploaded_file", None)
|
|
@@ -119,6 +117,7 @@ def maybe_configure_genai(key):
|
|
| 119 |
if not key or not HAS_GENAI:
|
| 120 |
return False
|
| 121 |
try:
|
|
|
|
| 122 |
genai.configure(api_key=key)
|
| 123 |
return True
|
| 124 |
except Exception:
|
|
@@ -138,43 +137,67 @@ def clear_all_video_state():
|
|
| 138 |
except Exception:
|
| 139 |
pass
|
| 140 |
|
| 141 |
-
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
-
|
| 147 |
-
|
| 148 |
-
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
)
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 172 |
def upload_video_sdk(filepath: str):
|
| 173 |
key = get_effective_api_key()
|
| 174 |
if not key:
|
| 175 |
raise RuntimeError("No API key provided")
|
| 176 |
if not HAS_GENAI or upload_file is None:
|
| 177 |
-
raise RuntimeError("google.generativeai SDK not available; cannot upload")
|
| 178 |
genai.configure(api_key=key)
|
| 179 |
return upload_file(filepath)
|
| 180 |
|
|
@@ -221,16 +244,66 @@ def remove_prompt_echo(prompt: str, text: str, check_len: int = 600, ratio_thres
|
|
| 221 |
return b_full[len(ph):].lstrip(" \n:-")
|
| 222 |
return text
|
| 223 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 224 |
col1, col2 = st.columns([1, 3])
|
| 225 |
with col1:
|
| 226 |
generate_now = st.button("Generate the story", type="primary", disabled=not bool(get_effective_api_key()))
|
| 227 |
with col2:
|
| 228 |
pass
|
| 229 |
|
|
|
|
| 230 |
if st.sidebar.button("Load Video", use_container_width=True):
|
| 231 |
try:
|
| 232 |
vpw = st.session_state.get("video-password", "")
|
| 233 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 234 |
st.session_state["videos"] = path
|
| 235 |
st.session_state["last_loaded_path"] = path
|
| 236 |
st.session_state.pop("uploaded_file", None)
|
|
@@ -270,6 +343,7 @@ if st.session_state["videos"]:
|
|
| 270 |
except Exception:
|
| 271 |
pass
|
| 272 |
|
|
|
|
| 273 |
if generate_now and not st.session_state.get("busy"):
|
| 274 |
if not st.session_state.get("videos"):
|
| 275 |
st.error("No video loaded. Use 'Load Video' in the sidebar.")
|
|
@@ -336,7 +410,12 @@ if generate_now and not st.session_state.get("busy"):
|
|
| 336 |
try:
|
| 337 |
if not HAS_GENAI or genai is None:
|
| 338 |
raise RuntimeError("Responses API not available; install google.generativeai SDK.")
|
| 339 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 340 |
fname = file_name_or_id(processed)
|
| 341 |
if not fname:
|
| 342 |
raise RuntimeError("Uploaded file missing name/id")
|
|
@@ -345,10 +424,12 @@ if generate_now and not st.session_state.get("busy"):
|
|
| 345 |
user_msg = {"role": "user", "content": "Please summarize the attached video."}
|
| 346 |
|
| 347 |
response = None
|
|
|
|
| 348 |
|
| 349 |
-
#
|
| 350 |
try:
|
| 351 |
if hasattr(genai, "responses") and hasattr(genai.responses, "generate"):
|
|
|
|
| 352 |
response = genai.responses.generate(
|
| 353 |
model=model_used,
|
| 354 |
messages=[system_msg, user_msg],
|
|
@@ -356,129 +437,135 @@ if generate_now and not st.session_state.get("busy"):
|
|
| 356 |
safety_settings=safety_settings,
|
| 357 |
max_output_tokens=max_tokens,
|
| 358 |
)
|
| 359 |
-
except Exception:
|
|
|
|
| 360 |
response = None
|
| 361 |
|
| 362 |
-
#
|
| 363 |
if response is None:
|
| 364 |
try:
|
| 365 |
if hasattr(genai, "GenerativeModel"):
|
|
|
|
| 366 |
gm = genai.GenerativeModel(model=model_used)
|
| 367 |
if hasattr(gm, "generate_content"):
|
| 368 |
response = gm.generate_content([system_msg, user_msg], files=[{"name": fname}], max_output_tokens=max_tokens)
|
| 369 |
elif hasattr(gm, "generate"):
|
| 370 |
response = gm.generate([system_msg, user_msg], files=[{"name": fname}], max_output_tokens=max_tokens)
|
| 371 |
-
except Exception:
|
|
|
|
| 372 |
response = None
|
| 373 |
|
| 374 |
-
#
|
| 375 |
if response is None:
|
| 376 |
try:
|
| 377 |
if hasattr(genai, "generate"):
|
|
|
|
| 378 |
response = genai.generate(model=model_used, input=[{"text": prompt_text, "files": [{"name": fname}]}], max_output_tokens=max_tokens)
|
| 379 |
elif hasattr(genai, "create"):
|
|
|
|
| 380 |
response = genai.create(model=model_used, input=[{"text": prompt_text, "files": [{"name": fname}]}], max_output_tokens=max_tokens)
|
| 381 |
-
except Exception:
|
|
|
|
| 382 |
response = None
|
| 383 |
|
| 384 |
-
#
|
| 385 |
if response is None:
|
| 386 |
-
# attempt to call GenerativeModel regardless if present (wrapped defensively)
|
| 387 |
try:
|
| 388 |
if hasattr(genai, "GenerativeModel"):
|
|
|
|
| 389 |
gm = genai.GenerativeModel(model=model_used)
|
| 390 |
-
# try generate_content/generate without raising
|
| 391 |
try:
|
| 392 |
response = gm.generate_content([system_msg, user_msg], files=[{"name": fname}], max_output_tokens=max_tokens)
|
| 393 |
except Exception:
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
|
| 397 |
-
response = None
|
| 398 |
-
except Exception:
|
| 399 |
response = None
|
| 400 |
|
| 401 |
if response is None:
|
| 402 |
-
#
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
for key in ("output", "candidates", "items", "responses"):
|
| 410 |
-
val = response.get(key)
|
| 411 |
-
if isinstance(val, (list, tuple)) and val:
|
| 412 |
-
outputs = list(val)
|
| 413 |
-
break
|
| 414 |
-
if not outputs:
|
| 415 |
-
for v in response.values():
|
| 416 |
-
if isinstance(v, (list, tuple)) and v:
|
| 417 |
-
outputs = list(v)
|
| 418 |
-
break
|
| 419 |
-
else:
|
| 420 |
-
for attr in ("output", "candidates", "items", "responses"):
|
| 421 |
-
val = getattr(response, attr, None)
|
| 422 |
-
if isinstance(val, (list, tuple)) and val:
|
| 423 |
-
try:
|
| 424 |
-
outputs = list(val)
|
| 425 |
-
except Exception:
|
| 426 |
-
outputs = val
|
| 427 |
-
break
|
| 428 |
-
except Exception:
|
| 429 |
outputs = []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 430 |
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
| 438 |
-
outputs = [{"text": candidate_text}]
|
| 439 |
-
|
| 440 |
-
text_pieces = []
|
| 441 |
-
for item in outputs:
|
| 442 |
-
if not item:
|
| 443 |
-
continue
|
| 444 |
-
if isinstance(item, dict):
|
| 445 |
-
for k in ("content", "text", "message", "output_text", "output"):
|
| 446 |
-
v = item.get(k)
|
| 447 |
-
if v:
|
| 448 |
-
if isinstance(v, str):
|
| 449 |
-
text_pieces.append(v.strip())
|
| 450 |
-
elif isinstance(v, (list, tuple)):
|
| 451 |
-
for e in v:
|
| 452 |
-
if isinstance(e, str):
|
| 453 |
-
text_pieces.append(e.strip())
|
| 454 |
-
elif isinstance(e, dict):
|
| 455 |
-
t = e.get("text") or e.get("content")
|
| 456 |
-
if t:
|
| 457 |
-
text_pieces.append(str(t).strip())
|
| 458 |
-
break
|
| 459 |
-
else:
|
| 460 |
-
for k in ("content", "text", "message", "output", "output_text"):
|
| 461 |
-
v = getattr(item, k, None)
|
| 462 |
-
if v:
|
| 463 |
-
if isinstance(v, str):
|
| 464 |
-
text_pieces.append(v.strip())
|
| 465 |
-
elif isinstance(v, (list, tuple)):
|
| 466 |
-
for e in v:
|
| 467 |
-
if isinstance(e, str):
|
| 468 |
-
text_pieces.append(e.strip())
|
| 469 |
-
else:
|
| 470 |
-
t = getattr(e, "text", None) or getattr(e, "content", None)
|
| 471 |
-
if t:
|
| 472 |
-
text_pieces.append(str(t).strip())
|
| 473 |
-
break
|
| 474 |
-
|
| 475 |
-
seen = set()
|
| 476 |
-
filtered = []
|
| 477 |
-
for t in text_pieces:
|
| 478 |
-
if t and t not in seen:
|
| 479 |
-
filtered.append(t)
|
| 480 |
-
seen.add(t)
|
| 481 |
-
out = "\n\n".join(filtered)
|
| 482 |
|
| 483 |
except Exception as e:
|
| 484 |
tb = traceback.format_exc()
|
|
|
|
| 8 |
from pathlib import Path
|
| 9 |
from difflib import SequenceMatcher
|
| 10 |
|
| 11 |
+
import requests
|
| 12 |
+
from bs4 import BeautifulSoup
|
| 13 |
+
|
| 14 |
import yt_dlp
|
| 15 |
import ffmpeg
|
| 16 |
import streamlit as st
|
|
|
|
| 18 |
|
| 19 |
load_dotenv()
|
| 20 |
|
| 21 |
+
# phi agent removed to avoid fragile imports in varied environments
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
HAS_PHI = False
|
| 23 |
|
| 24 |
+
# google generative ai SDK (may be absent or partial in some runtimes)
|
| 25 |
try:
|
| 26 |
+
import google.generativeai as genai # type: ignore
|
| 27 |
from google.generativeai import upload_file, get_file # type: ignore
|
| 28 |
HAS_GENAI = True
|
| 29 |
except Exception:
|
| 30 |
genai = None
|
| 31 |
+
upload_file = None
|
| 32 |
+
get_file = None
|
| 33 |
HAS_GENAI = False
|
| 34 |
|
| 35 |
st.set_page_config(page_title="Generate the story of videos", layout="wide")
|
| 36 |
DATA_DIR = Path("./data")
|
| 37 |
DATA_DIR.mkdir(exist_ok=True)
|
| 38 |
|
| 39 |
+
# Session defaults
|
| 40 |
st.session_state.setdefault("videos", "")
|
| 41 |
st.session_state.setdefault("loop_video", False)
|
| 42 |
st.session_state.setdefault("uploaded_file", None)
|
|
|
|
| 117 |
if not key or not HAS_GENAI:
|
| 118 |
return False
|
| 119 |
try:
|
| 120 |
+
# defensive configuration (some envs require configure)
|
| 121 |
genai.configure(api_key=key)
|
| 122 |
return True
|
| 123 |
except Exception:
|
|
|
|
| 137 |
except Exception:
|
| 138 |
pass
|
| 139 |
|
| 140 |
+
# URL expand + extraction helpers (integrated into Load Video)
|
| 141 |
+
def expand_url(short_url, timeout=10):
|
| 142 |
+
try:
|
| 143 |
+
r = requests.get(short_url, allow_redirects=True, timeout=timeout, headers={"User-Agent":"Mozilla/5.0"})
|
| 144 |
+
final_url = r.url
|
| 145 |
+
return final_url, r.text
|
| 146 |
+
except Exception as e:
|
| 147 |
+
return None, f"error: {e}"
|
| 148 |
+
|
| 149 |
+
def extract_video_from_html(html, base_url=None):
|
| 150 |
+
soup = BeautifulSoup(html, "html.parser")
|
| 151 |
+
# 1) Open Graph video
|
| 152 |
+
og = soup.find("meta", property="og:video")
|
| 153 |
+
if og and og.get("content"):
|
| 154 |
+
return og.get("content")
|
| 155 |
+
# 2) Look for video tags
|
| 156 |
+
vtag = soup.find("video")
|
| 157 |
+
if vtag:
|
| 158 |
+
src = vtag.get("src")
|
| 159 |
+
if src:
|
| 160 |
+
return src
|
| 161 |
+
# source children
|
| 162 |
+
source = vtag.find("source")
|
| 163 |
+
if source and source.get("src"):
|
| 164 |
+
return source.get("src")
|
| 165 |
+
# 3) JSON-LD or structured data with video
|
| 166 |
+
for script in soup.find_all("script", type="application/ld+json"):
|
| 167 |
+
try:
|
| 168 |
+
import json
|
| 169 |
+
data = json.loads(script.string or "{}")
|
| 170 |
+
# common pattern
|
| 171 |
+
if isinstance(data, dict):
|
| 172 |
+
video = data.get("video") or data.get("videoObject") or data.get("mainEntity")
|
| 173 |
+
if isinstance(video, dict):
|
| 174 |
+
for k in ("contentUrl", "url"):
|
| 175 |
+
if video.get(k):
|
| 176 |
+
return video.get(k)
|
| 177 |
+
# top-level contentUrl
|
| 178 |
+
if data.get("contentUrl"):
|
| 179 |
+
return data.get("contentUrl")
|
| 180 |
+
except Exception:
|
| 181 |
+
continue
|
| 182 |
+
# 4) look for meta property site-specific fallbacks
|
| 183 |
+
for meta_name in ("twitter:player:stream", "twitter:player"):
|
| 184 |
+
m = soup.find("meta", attrs={"name": meta_name})
|
| 185 |
+
if m and m.get("content"):
|
| 186 |
+
return m.get("content")
|
| 187 |
+
# fallback: search for direct links to common video hosts (youtube, vimeo) in anchor tags
|
| 188 |
+
for a in soup.find_all("a", href=True):
|
| 189 |
+
href = a["href"]
|
| 190 |
+
if any(domain in href for domain in ("youtube.com", "youtu.be", "vimeo.com")):
|
| 191 |
+
return href
|
| 192 |
+
return None
|
| 193 |
+
|
| 194 |
+
# When SDK has upload_file/get_file, use them; else raise when needed
|
| 195 |
def upload_video_sdk(filepath: str):
|
| 196 |
key = get_effective_api_key()
|
| 197 |
if not key:
|
| 198 |
raise RuntimeError("No API key provided")
|
| 199 |
if not HAS_GENAI or upload_file is None:
|
| 200 |
+
raise RuntimeError("google.generativeai SDK upload not available; cannot upload")
|
| 201 |
genai.configure(api_key=key)
|
| 202 |
return upload_file(filepath)
|
| 203 |
|
|
|
|
| 244 |
return b_full[len(ph):].lstrip(" \n:-")
|
| 245 |
return text
|
| 246 |
|
| 247 |
+
# UI layout
|
| 248 |
+
current_url = st.session_state.get("url", "")
|
| 249 |
+
if current_url != st.session_state.get("last_url_value"):
|
| 250 |
+
clear_all_video_state()
|
| 251 |
+
st.session_state["last_url_value"] = current_url
|
| 252 |
+
|
| 253 |
+
st.sidebar.header("Video Input")
|
| 254 |
+
st.sidebar.text_input("Video URL", key="url", placeholder="https://")
|
| 255 |
+
|
| 256 |
+
settings_exp = st.sidebar.expander("Settings", expanded=False)
|
| 257 |
+
settings_exp.text_input("Gemini Model (short name)", "gemini-2.5-flash-lite", key="model_input")
|
| 258 |
+
settings_exp.text_input("Google API Key", key="api_key", value=os.getenv("GOOGLE_API_KEY", ""), type="password")
|
| 259 |
+
default_prompt = (
|
| 260 |
+
"Watch the video and provide a detailed behavioral report focusing on human actions, interactions, posture, movement, and apparent intent. Keep language professional. Include a list of observations for notable events."
|
| 261 |
+
)
|
| 262 |
+
settings_exp.text_area("Enter analysis", value=default_prompt, height=140, key="analysis_prompt")
|
| 263 |
+
settings_exp.text_input("Video Password (if needed)", key="video-password", placeholder="password", type="password")
|
| 264 |
+
settings_exp.checkbox("Fast mode (skip compression, smaller model, fewer tokens)", key="fast_mode")
|
| 265 |
+
|
| 266 |
+
key_source = "session" if st.session_state.get("api_key") else ".env" if os.getenv("GOOGLE_API_KEY") else "none"
|
| 267 |
+
settings_exp.caption(f"Using API key from: **{key_source}**")
|
| 268 |
+
|
| 269 |
+
if not get_effective_api_key():
|
| 270 |
+
settings_exp.warning("No Google API key provided; upload/generation disabled.", icon="⚠️")
|
| 271 |
+
|
| 272 |
+
safety_settings = [
|
| 273 |
+
{"category": "HARM_CATEGORY_HARASSMENT", "threshold": "OFF"},
|
| 274 |
+
{"category": "HARM_CATEGORY_HATE_SPEECH", "threshold": "OFF"},
|
| 275 |
+
{"category": "HARM_CATEGORY_SEXUALLY_EXPLICIT", "threshold": "OFF"},
|
| 276 |
+
{"category": "HARM_CATEGORY_DANGEROUS_CONTENT", "threshold": "OFF"},
|
| 277 |
+
]
|
| 278 |
+
|
| 279 |
col1, col2 = st.columns([1, 3])
|
| 280 |
with col1:
|
| 281 |
generate_now = st.button("Generate the story", type="primary", disabled=not bool(get_effective_api_key()))
|
| 282 |
with col2:
|
| 283 |
pass
|
| 284 |
|
| 285 |
+
# Load Video flow: expand short URLs and try to extract direct video links from HTML before yt-dlp
|
| 286 |
if st.sidebar.button("Load Video", use_container_width=True):
|
| 287 |
try:
|
| 288 |
vpw = st.session_state.get("video-password", "")
|
| 289 |
+
url_val = st.session_state.get("url", "").strip()
|
| 290 |
+
# If URL present, try to expand and extract video from HTML first
|
| 291 |
+
final_url = url_val
|
| 292 |
+
html_text = None
|
| 293 |
+
if url_val:
|
| 294 |
+
expanded, html_or_err = expand_url(url_val)
|
| 295 |
+
if expanded:
|
| 296 |
+
final_url = expanded
|
| 297 |
+
html_text = html_or_err
|
| 298 |
+
else:
|
| 299 |
+
# expansion failed but html_or_err contains error message; ignore
|
| 300 |
+
html_text = None
|
| 301 |
+
# If we have HTML, try to find direct video link
|
| 302 |
+
extracted = None
|
| 303 |
+
if html_text:
|
| 304 |
+
extracted = extract_video_from_html(html_text, base_url=final_url)
|
| 305 |
+
target_url_for_ytdlp = extracted or final_url
|
| 306 |
+
path = download_video_ytdlp(target_url_for_ytdlp, str(DATA_DIR), vpw)
|
| 307 |
st.session_state["videos"] = path
|
| 308 |
st.session_state["last_loaded_path"] = path
|
| 309 |
st.session_state.pop("uploaded_file", None)
|
|
|
|
| 343 |
except Exception:
|
| 344 |
pass
|
| 345 |
|
| 346 |
+
# Generation flow (robust handling of google.generativeai variants)
|
| 347 |
if generate_now and not st.session_state.get("busy"):
|
| 348 |
if not st.session_state.get("videos"):
|
| 349 |
st.error("No video loaded. Use 'Load Video' in the sidebar.")
|
|
|
|
| 410 |
try:
|
| 411 |
if not HAS_GENAI or genai is None:
|
| 412 |
raise RuntimeError("Responses API not available; install google.generativeai SDK.")
|
| 413 |
+
# ensure configured
|
| 414 |
+
try:
|
| 415 |
+
genai.configure(api_key=key_to_use)
|
| 416 |
+
except Exception:
|
| 417 |
+
pass
|
| 418 |
+
|
| 419 |
fname = file_name_or_id(processed)
|
| 420 |
if not fname:
|
| 421 |
raise RuntimeError("Uploaded file missing name/id")
|
|
|
|
| 424 |
user_msg = {"role": "user", "content": "Please summarize the attached video."}
|
| 425 |
|
| 426 |
response = None
|
| 427 |
+
diagnostics = {"attempts": []}
|
| 428 |
|
| 429 |
+
# Attempt #1: genai.responses.generate (modern)
|
| 430 |
try:
|
| 431 |
if hasattr(genai, "responses") and hasattr(genai.responses, "generate"):
|
| 432 |
+
diagnostics["attempts"].append("responses.generate")
|
| 433 |
response = genai.responses.generate(
|
| 434 |
model=model_used,
|
| 435 |
messages=[system_msg, user_msg],
|
|
|
|
| 437 |
safety_settings=safety_settings,
|
| 438 |
max_output_tokens=max_tokens,
|
| 439 |
)
|
| 440 |
+
except Exception as e:
|
| 441 |
+
diagnostics["responses.generate_error"] = str(e)
|
| 442 |
response = None
|
| 443 |
|
| 444 |
+
# Attempt #2: GenerativeModel variants (0.8.x+)
|
| 445 |
if response is None:
|
| 446 |
try:
|
| 447 |
if hasattr(genai, "GenerativeModel"):
|
| 448 |
+
diagnostics["attempts"].append("GenerativeModel")
|
| 449 |
gm = genai.GenerativeModel(model=model_used)
|
| 450 |
if hasattr(gm, "generate_content"):
|
| 451 |
response = gm.generate_content([system_msg, user_msg], files=[{"name": fname}], max_output_tokens=max_tokens)
|
| 452 |
elif hasattr(gm, "generate"):
|
| 453 |
response = gm.generate([system_msg, user_msg], files=[{"name": fname}], max_output_tokens=max_tokens)
|
| 454 |
+
except Exception as e:
|
| 455 |
+
diagnostics["GenerativeModel_error"] = str(e)
|
| 456 |
response = None
|
| 457 |
|
| 458 |
+
# Attempt #3: top-level legacy helpers
|
| 459 |
if response is None:
|
| 460 |
try:
|
| 461 |
if hasattr(genai, "generate"):
|
| 462 |
+
diagnostics["attempts"].append("top.generate")
|
| 463 |
response = genai.generate(model=model_used, input=[{"text": prompt_text, "files": [{"name": fname}]}], max_output_tokens=max_tokens)
|
| 464 |
elif hasattr(genai, "create"):
|
| 465 |
+
diagnostics["attempts"].append("top.create")
|
| 466 |
response = genai.create(model=model_used, input=[{"text": prompt_text, "files": [{"name": fname}]}], max_output_tokens=max_tokens)
|
| 467 |
+
except Exception as e:
|
| 468 |
+
diagnostics["top_level_error"] = str(e)
|
| 469 |
response = None
|
| 470 |
|
| 471 |
+
# Final defensive tries for known objects
|
| 472 |
if response is None:
|
|
|
|
| 473 |
try:
|
| 474 |
if hasattr(genai, "GenerativeModel"):
|
| 475 |
+
diagnostics["attempts"].append("GenerativeModel_last")
|
| 476 |
gm = genai.GenerativeModel(model=model_used)
|
|
|
|
| 477 |
try:
|
| 478 |
response = gm.generate_content([system_msg, user_msg], files=[{"name": fname}], max_output_tokens=max_tokens)
|
| 479 |
except Exception:
|
| 480 |
+
response = gm.generate([system_msg, user_msg], files=[{"name": fname}], max_output_tokens=max_tokens)
|
| 481 |
+
except Exception as e:
|
| 482 |
+
diagnostics["GenerativeModel_last_error"] = str(e)
|
|
|
|
|
|
|
| 483 |
response = None
|
| 484 |
|
| 485 |
if response is None:
|
| 486 |
+
# Instead of raising the runtime error seen previously, attach diagnostics to last_error and return gracefully
|
| 487 |
+
diag_text = f"No supported generate method found on google.generativeai in this runtime. Diagnostics: {diagnostics}"
|
| 488 |
+
st.session_state["last_error"] = diag_text
|
| 489 |
+
st.error("Responses API not supported in this runtime. See Last Error for details.")
|
| 490 |
+
out = ""
|
| 491 |
+
else:
|
| 492 |
+
# Normalize outputs into text pieces
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 493 |
outputs = []
|
| 494 |
+
try:
|
| 495 |
+
if isinstance(response, dict):
|
| 496 |
+
for key in ("output", "candidates", "items", "responses"):
|
| 497 |
+
val = response.get(key)
|
| 498 |
+
if isinstance(val, (list, tuple)) and val:
|
| 499 |
+
outputs = list(val)
|
| 500 |
+
break
|
| 501 |
+
if not outputs:
|
| 502 |
+
for v in response.values():
|
| 503 |
+
if isinstance(v, (list, tuple)) and v:
|
| 504 |
+
outputs = list(v)
|
| 505 |
+
break
|
| 506 |
+
else:
|
| 507 |
+
for attr in ("output", "candidates", "items", "responses"):
|
| 508 |
+
val = getattr(response, attr, None)
|
| 509 |
+
if isinstance(val, (list, tuple)) and val:
|
| 510 |
+
try:
|
| 511 |
+
outputs = list(val)
|
| 512 |
+
except Exception:
|
| 513 |
+
outputs = val
|
| 514 |
+
break
|
| 515 |
+
except Exception:
|
| 516 |
+
outputs = []
|
| 517 |
+
|
| 518 |
+
if not outputs:
|
| 519 |
+
candidate_text = None
|
| 520 |
+
if isinstance(response, dict):
|
| 521 |
+
candidate_text = response.get("text") or response.get("message")
|
| 522 |
+
else:
|
| 523 |
+
candidate_text = getattr(response, "text", None) or getattr(response, "message", None)
|
| 524 |
+
if candidate_text:
|
| 525 |
+
outputs = [{"text": candidate_text}]
|
| 526 |
+
|
| 527 |
+
text_pieces = []
|
| 528 |
+
for item in outputs:
|
| 529 |
+
if not item:
|
| 530 |
+
continue
|
| 531 |
+
if isinstance(item, dict):
|
| 532 |
+
for k in ("content", "text", "message", "output_text", "output"):
|
| 533 |
+
v = item.get(k)
|
| 534 |
+
if v:
|
| 535 |
+
if isinstance(v, str):
|
| 536 |
+
text_pieces.append(v.strip())
|
| 537 |
+
elif isinstance(v, (list, tuple)):
|
| 538 |
+
for e in v:
|
| 539 |
+
if isinstance(e, str):
|
| 540 |
+
text_pieces.append(e.strip())
|
| 541 |
+
elif isinstance(e, dict):
|
| 542 |
+
t = e.get("text") or e.get("content")
|
| 543 |
+
if t:
|
| 544 |
+
text_pieces.append(str(t).strip())
|
| 545 |
+
break
|
| 546 |
+
else:
|
| 547 |
+
for k in ("content", "text", "message", "output", "output_text"):
|
| 548 |
+
v = getattr(item, k, None)
|
| 549 |
+
if v:
|
| 550 |
+
if isinstance(v, str):
|
| 551 |
+
text_pieces.append(v.strip())
|
| 552 |
+
elif isinstance(v, (list, tuple)):
|
| 553 |
+
for e in v:
|
| 554 |
+
if isinstance(e, str):
|
| 555 |
+
text_pieces.append(e.strip())
|
| 556 |
+
else:
|
| 557 |
+
t = getattr(e, "text", None) or getattr(e, "content", None)
|
| 558 |
+
if t:
|
| 559 |
+
text_pieces.append(str(t).strip())
|
| 560 |
+
break
|
| 561 |
|
| 562 |
+
seen = set()
|
| 563 |
+
filtered = []
|
| 564 |
+
for t in text_pieces:
|
| 565 |
+
if t and t not in seen:
|
| 566 |
+
filtered.append(t)
|
| 567 |
+
seen.add(t)
|
| 568 |
+
out = "\n\n".join(filtered)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 569 |
|
| 570 |
except Exception as e:
|
| 571 |
tb = traceback.format_exc()
|