CB commited on
Commit
3c37e6c
·
verified ·
1 Parent(s): 35c39c2

Update streamlit_app.py

Browse files
Files changed (1) hide show
  1. streamlit_app.py +93 -18
streamlit_app.py CHANGED
@@ -8,12 +8,14 @@ from glob import glob
8
  from pathlib import Path
9
  import json
10
  import logging
 
11
 
12
  import yt_dlp
13
  import ffmpeg
14
  import streamlit as st
15
  from dotenv import load_dotenv
16
  from difflib import SequenceMatcher
 
17
 
18
  # Try import google.generativeai, support multiple SDK shapes
19
  try:
@@ -264,25 +266,60 @@ def _normalize_genai_response(response):
264
  seen.add(t)
265
  return "\n\n".join(filtered).strip()
266
 
267
- # Generation (supports various SDK shapes)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
  def generate_via_responses_api(prompt_text: str, processed, model_used: str, max_tokens: int = 1024, timeout: int = 300, progress_callback=None):
269
  key = get_effective_api_key()
270
  if not key:
271
  raise RuntimeError("No API key provided")
272
- if not HAS_GENAI or genai is None:
273
- raise RuntimeError("Responses API not available; install google-generativeai SDK.")
274
  if genai is not None and hasattr(genai, "configure"):
275
- genai.configure(api_key=key)
276
- fname = file_name_or_id(processed)
277
- if not fname:
278
- raise RuntimeError("Uploaded file missing name/id")
 
279
 
 
280
  system_msg = {"role": "system", "content": prompt_text}
281
- user_msg = {"role": "user", "content": "Please summarize the attached video."}
282
 
283
  call_variants = [
284
- ("responses.generate", {"model": model_used, "messages": [system_msg, user_msg], "files": [{"name": fname}], "max_output_tokens": max_tokens}),
285
- ("responses.generate_alt", {"model": model_used, "input": [{"text": prompt_text, "files": [{"name": fname}]}], "max_output_tokens": max_tokens}),
286
  ("legacy_create", {"model": model_used, "input": prompt_text, "file": fname, "max_output_tokens": max_tokens}),
287
  ]
288
 
@@ -290,10 +327,33 @@ def generate_via_responses_api(prompt_text: str, processed, model_used: str, max
290
  txt = str(e_text).lower()
291
  return any(k in txt for k in ("internal", "unavailable", "deadlineexceeded", "deadline exceeded", "timeout", "rate limit", "503", "502", "500"))
292
 
 
 
 
 
 
 
 
 
 
 
 
 
293
  start = time.time()
294
  last_exc = None
295
  backoff = 1.0
296
  attempts = 0
 
 
 
 
 
 
 
 
 
 
 
297
  while True:
298
  for method_name, payload in call_variants:
299
  attempts += 1
@@ -303,18 +363,26 @@ def generate_via_responses_api(prompt_text: str, processed, model_used: str, max
303
 
304
  # Preferred modern: genai.responses.generate or genai_responses.generate
305
  if genai_responses is not None and hasattr(genai_responses, "generate"):
 
 
306
  resp = genai_responses.generate(**payload)
307
  text = _normalize_genai_response(resp)
308
  if progress_callback:
309
  progress_callback("done", int(time.time() - start), {"method": method_name})
 
 
 
310
  return text
311
 
312
  # Older path: genai.Responses.create
313
  if hasattr(genai, "Responses") and hasattr(genai.Responses, "create"):
 
314
  resp = genai.Responses.create(**payload) # type: ignore
315
  text = _normalize_genai_response(resp)
316
  if progress_callback:
317
  progress_callback("done", int(time.time() - start), {"method": method_name})
 
 
318
  return text
319
 
320
  # Fallback: GenerativeModel API (ChatSession). This SDK's ChatSession.send_message may not accept timeout kw.
@@ -323,7 +391,6 @@ def generate_via_responses_api(prompt_text: str, processed, model_used: str, max
323
  model_obj = genai.GenerativeModel(model_name=model_used)
324
  if hasattr(model_obj, "start_chat"):
325
  chat = model_obj.start_chat()
326
- # Some SDKs' send_message signature differs; call without timeout kw when necessary.
327
  send = getattr(chat, "send_message", None)
328
  if send is None:
329
  raise RuntimeError("ChatSession has no send_message")
@@ -335,6 +402,8 @@ def generate_via_responses_api(prompt_text: str, processed, model_used: str, max
335
  text = text if text else _normalize_genai_response(resp)
336
  if progress_callback:
337
  progress_callback("done", int(time.time() - start), {"method": "GenerativeModel.chat"})
 
 
338
  return text
339
  except Exception:
340
  logger.exception("GenerativeModel.chat fallback failed")
@@ -345,10 +414,11 @@ def generate_via_responses_api(prompt_text: str, processed, model_used: str, max
345
  msg = str(e)
346
  logger.warning("Generation error (model=%s attempt=%s method=%s): %s", model_used, attempts, method_name, msg)
347
  if not is_transient_error(msg):
348
- if "No supported response generation method" in msg or "has no attribute" in msg:
 
349
  raise RuntimeError(
350
- "Installed google-generativeai package does not expose a compatible Responses API. "
351
- "Please upgrade to a recent release: pip install --upgrade google-generativeai"
352
  ) from e
353
  raise
354
  if time.time() - start > timeout:
@@ -529,8 +599,8 @@ if generate_now and not st.session_state.get("busy"):
529
  reupload_needed = False
530
 
531
  if reupload_needed:
532
- if not HAS_GENAI:
533
- raise RuntimeError("google.generativeai SDK not available; install it.")
534
  local_path = current_path
535
 
536
  try:
@@ -550,7 +620,12 @@ if generate_now and not st.session_state.get("busy"):
550
 
551
  with st.spinner(f"Uploading video{' (compressed)' if compressed else ''}..."):
552
  try:
553
- uploaded = upload_video_sdk(upload_path)
 
 
 
 
 
554
  except Exception as e:
555
  st.session_state["last_error"] = f"Upload failed: {e}\n\nTraceback:\n{traceback.format_exc()}"
556
  st.error("Upload failed. See Last Error for details.")
@@ -593,7 +668,7 @@ if generate_now and not st.session_state.get("busy"):
593
  gen_status.text(f"Stage: {stage} — elapsed: {elapsed}s — {info}")
594
  except Exception:
595
  pass
596
- out = generate_via_responses_api(prompt_text, processed, model_used, max_tokens=max_tokens, timeout=st.session_state.get("generation_timeout", 300), progress_callback=gen_progress_cb)
597
  gen_progress_placeholder.text(f"Generation complete in {int(time.time()-start_gen)}s")
598
  except Exception as e:
599
  tb = traceback.format_exc()
 
8
  from pathlib import Path
9
  import json
10
  import logging
11
+ import mimetypes
12
 
13
  import yt_dlp
14
  import ffmpeg
15
  import streamlit as st
16
  from dotenv import load_dotenv
17
  from difflib import SequenceMatcher
18
+ import requests
19
 
20
  # Try import google.generativeai, support multiple SDK shapes
21
  try:
 
266
  seen.add(t)
267
  return "\n\n".join(filtered).strip()
268
 
269
+ # REST fallback to GenAI Responses API
270
+ def rest_responses_api(prompt_text: str, file_path: str, model: str, max_tokens: int = 1024, timeout: int = 300, progress_callback=None):
271
+ key = get_effective_api_key()
272
+ if not key:
273
+ raise RuntimeError("No API key provided")
274
+ url = "https://generativelanguage.googleapis.com/v1beta2/responses:generate"
275
+ headers = {"Authorization": f"Bearer {key}"}
276
+ # Build a simple request that attaches the file as a "file" in multipart/form-data.
277
+ # Use a minimal JSON payload referencing the file by name in the input.
278
+ fname = Path(file_path).name
279
+ input_json = {
280
+ "model": model,
281
+ "input": [
282
+ {
283
+ "text": prompt_text,
284
+ "mimeType": mimetypes.guess_type(file_path)[0] or "application/octet-stream",
285
+ "attachments": [{"contentType": mimetypes.guess_type(file_path)[0] or "application/octet-stream", "name": fname}],
286
+ }
287
+ ],
288
+ "maxOutputTokens": max_tokens,
289
+ }
290
+ # Multipart: one part "request" with JSON, another with the file binary.
291
+ try:
292
+ with open(file_path, "rb") as f:
293
+ files = {
294
+ "request": ("request", json.dumps(input_json), "application/json"),
295
+ "file": (fname, f, mimetypes.guess_type(file_path)[0] or "application/octet-stream"),
296
+ }
297
+ resp = requests.post(url, headers=headers, files=files, timeout=timeout)
298
+ resp.raise_for_status()
299
+ data = resp.json()
300
+ return _normalize_genai_response(data)
301
+ except Exception as e:
302
+ raise RuntimeError(f"REST Responses API failed: {e}")
303
+
304
+ # Generation (supports various SDK shapes + REST fallback)
305
  def generate_via_responses_api(prompt_text: str, processed, model_used: str, max_tokens: int = 1024, timeout: int = 300, progress_callback=None):
306
  key = get_effective_api_key()
307
  if not key:
308
  raise RuntimeError("No API key provided")
 
 
309
  if genai is not None and hasattr(genai, "configure"):
310
+ try:
311
+ genai.configure(api_key=key)
312
+ except Exception:
313
+ pass
314
+ fname = file_name_or_id(processed) or None
315
 
316
+ # Prepare simple system+user structure
317
  system_msg = {"role": "system", "content": prompt_text}
318
+ user_msg = {"role": "user", "content": f"Please summarize the attached video: {fname or '[uploaded file]'}."}
319
 
320
  call_variants = [
321
+ ("responses.generate", {"model": model_used, "messages": [system_msg, user_msg], "files": [{"name": fname}] if fname else None, "max_output_tokens": max_tokens}),
322
+ ("responses.generate_alt", {"model": model_used, "input": [{"text": prompt_text, "files": [{"name": fname}]}] if fname else None, "max_output_tokens": max_tokens}),
323
  ("legacy_create", {"model": model_used, "input": prompt_text, "file": fname, "max_output_tokens": max_tokens}),
324
  ]
325
 
 
327
  txt = str(e_text).lower()
328
  return any(k in txt for k in ("internal", "unavailable", "deadlineexceeded", "deadline exceeded", "timeout", "rate limit", "503", "502", "500"))
329
 
330
+ # Quick pre-check: if processed is a local path (dictless), prefer REST fallback to ensure attachment works
331
+ local_file_path = None
332
+ if isinstance(processed, str) and os.path.exists(processed):
333
+ local_file_path = processed
334
+ elif isinstance(processed, dict):
335
+ # if SDK provided a dict with local path info (rare), try to detect
336
+ for k in ("path", "name", "filename", "uri"):
337
+ v = processed.get(k)
338
+ if isinstance(v, str) and os.path.exists(v):
339
+ local_file_path = v
340
+ break
341
+
342
  start = time.time()
343
  last_exc = None
344
  backoff = 1.0
345
  attempts = 0
346
+
347
+ # If we have a local file path, try REST fallback first for reliable file attachment.
348
+ if local_file_path:
349
+ try:
350
+ if progress_callback:
351
+ progress_callback("rest-fallback", 0, {"file": local_file_path, "model": model_used})
352
+ return rest_responses_api(prompt_text, local_file_path, model_used, max_tokens=max_tokens, timeout=timeout, progress_callback=progress_callback)
353
+ except Exception as e:
354
+ last_exc = e
355
+ logger.warning("REST fallback failed; will try SDK: %s", e)
356
+
357
  while True:
358
  for method_name, payload in call_variants:
359
  attempts += 1
 
363
 
364
  # Preferred modern: genai.responses.generate or genai_responses.generate
365
  if genai_responses is not None and hasattr(genai_responses, "generate"):
366
+ # Remove None entries from payload
367
+ payload = {k: v for k, v in payload.items() if v is not None}
368
  resp = genai_responses.generate(**payload)
369
  text = _normalize_genai_response(resp)
370
  if progress_callback:
371
  progress_callback("done", int(time.time() - start), {"method": method_name})
372
+ # If the model returns a request-for-file style message, try REST fallback
373
+ if text and ("please provide the video" in text.lower() or "upload the video" in text.lower()):
374
+ raise RuntimeError("Model indicates it didn't receive the file")
375
  return text
376
 
377
  # Older path: genai.Responses.create
378
  if hasattr(genai, "Responses") and hasattr(genai.Responses, "create"):
379
+ payload = {k: v for k, v in payload.items() if v is not None}
380
  resp = genai.Responses.create(**payload) # type: ignore
381
  text = _normalize_genai_response(resp)
382
  if progress_callback:
383
  progress_callback("done", int(time.time() - start), {"method": method_name})
384
+ if text and ("please provide the video" in text.lower() or "upload the video" in text.lower()):
385
+ raise RuntimeError("Model indicates it didn't receive the file")
386
  return text
387
 
388
  # Fallback: GenerativeModel API (ChatSession). This SDK's ChatSession.send_message may not accept timeout kw.
 
391
  model_obj = genai.GenerativeModel(model_name=model_used)
392
  if hasattr(model_obj, "start_chat"):
393
  chat = model_obj.start_chat()
 
394
  send = getattr(chat, "send_message", None)
395
  if send is None:
396
  raise RuntimeError("ChatSession has no send_message")
 
402
  text = text if text else _normalize_genai_response(resp)
403
  if progress_callback:
404
  progress_callback("done", int(time.time() - start), {"method": "GenerativeModel.chat"})
405
+ if text and ("please provide the video" in text.lower() or "upload the video" in text.lower()):
406
+ raise RuntimeError("Model indicates it didn't receive the file")
407
  return text
408
  except Exception:
409
  logger.exception("GenerativeModel.chat fallback failed")
 
414
  msg = str(e)
415
  logger.warning("Generation error (model=%s attempt=%s method=%s): %s", model_used, attempts, method_name, msg)
416
  if not is_transient_error(msg):
417
+ if "No supported response generation method" in msg or "has no attribute" in msg or "didn't receive the file" in msg:
418
+ # If it's a file-attachment issue or incompatible SDK, offer a clear upgrade message (but don't spam UI)
419
  raise RuntimeError(
420
+ "Installed google-generativeai package may not expose a compatible Responses API or the SDK didn't attach the file correctly. "
421
+ "Try upgrading the SDK: pip install --upgrade google-generativeai, or use the app's REST fallback."
422
  ) from e
423
  raise
424
  if time.time() - start > timeout:
 
599
  reupload_needed = False
600
 
601
  if reupload_needed:
602
+ if not HAS_GENAI and not get_effective_api_key():
603
+ raise RuntimeError("google.generativeai SDK not available and no API key; cannot upload")
604
  local_path = current_path
605
 
606
  try:
 
620
 
621
  with st.spinner(f"Uploading video{' (compressed)' if compressed else ''}..."):
622
  try:
623
+ # Prefer SDK upload if available, else keep local path for REST fallback
624
+ if HAS_GENAI and upload_file is not None:
625
+ uploaded = upload_video_sdk(upload_path)
626
+ else:
627
+ # No SDK upload; retain local path (REST fallback will attach file directly)
628
+ uploaded = upload_path
629
  except Exception as e:
630
  st.session_state["last_error"] = f"Upload failed: {e}\n\nTraceback:\n{traceback.format_exc()}"
631
  st.error("Upload failed. See Last Error for details.")
 
668
  gen_status.text(f"Stage: {stage} — elapsed: {elapsed}s — {info}")
669
  except Exception:
670
  pass
671
+ out = generate_via_responses_api(prompt_text, st.session_state.get("processed_file"), model_used, max_tokens=max_tokens, timeout=st.session_state.get("generation_timeout", 300), progress_callback=gen_progress_cb)
672
  gen_progress_placeholder.text(f"Generation complete in {int(time.time()-start_gen)}s")
673
  except Exception as e:
674
  tb = traceback.format_exc()