Spaces:
Sleeping
Sleeping
| """ | |
| Video Explainer Chat Handler - answers questions using video title, subject, lesson, and associated file. | |
| Uses OpenAI API (credentials from OPENAI_API_KEY environment variable). | |
| Laravel sends file content as base64 (since it may be on a different host). | |
| """ | |
| import base64 | |
| import os | |
| import tempfile | |
| from openai import OpenAI | |
| from file_processor import extract_text | |
| api_key = os.getenv("OPENAI_API_KEY") | |
| if api_key == "PASTE_YOUR_KEY_HERE": | |
| print("WARNING: Set OPENAI_API_KEY or OPENAI_API_KEY_VIDEO environment variable") | |
| client = OpenAI(api_key=api_key) | |
| def get_text_from_base64_file(file_content_base64: str, file_filename: str) -> str: | |
| """ | |
| Decode base64 file content, write to temp file, extract text, and return. | |
| Returns empty string on any error. | |
| """ | |
| if not file_content_base64 or not file_filename: | |
| return "" | |
| try: | |
| raw = base64.b64decode(file_content_base64, validate=True) | |
| except Exception: | |
| return "" | |
| if not raw: | |
| return "" | |
| suffix = os.path.splitext(file_filename)[1] if file_filename else ".bin" | |
| if not suffix or suffix == ".": | |
| suffix = ".bin" | |
| tmp_path = None | |
| try: | |
| fd, tmp_path = tempfile.mkstemp(suffix=suffix) | |
| os.write(fd, raw) | |
| os.close(fd) | |
| return extract_text(tmp_path) | |
| except Exception: | |
| return "" | |
| finally: | |
| if tmp_path and os.path.exists(tmp_path): | |
| try: | |
| os.unlink(tmp_path) | |
| except OSError: | |
| pass | |
| def chat_video_explainer(question, title, subject, lesson, file_content_base64=None, file_filename=None): | |
| """ | |
| Answer question using video metadata (title, subject, lesson) and associated file content. | |
| File content is received as base64 from Laravel (different hosting). | |
| Returns (answer, file_fetched). | |
| """ | |
| file_content = get_text_from_base64_file( | |
| file_content_base64 or "", file_filename or "" | |
| ) | |
| file_fetched = bool(file_content.strip()) | |
| has_file = file_fetched | |
| system_prompt = """You are a video assistant. You receive VIDEO TITLE, SUBJECT, LESSON/TOPIC, and ASSOCIATED MATERIAL (parsed file content). | |
| CRITICAL RULES: | |
| 1. Always use the provided context. Title, subject, and lesson are always sent—use them. | |
| 2. When ASSOCIATED MATERIAL is provided: It contains parsed content from the video's uploaded file (notes, transcript, slides). Use it for summarize, explain, and all answers. Give concrete summaries and quote specific points. | |
| 3. When asked to "summarize" or "explain the video": If you have ASSOCIATED MATERIAL, summarize that content and tie it to the title, subject, and lesson. If you have NO associated material, summarize what you know: "This video (title: X) covers [subject] — [lesson/topic]. No transcript or notes were provided, so I can't detail the full content. I can help with questions about [topic]." | |
| 4. Never say "I don't have access" or "I don't have notes" when ASSOCIATED MATERIAL is in the context. | |
| 5. Use clear, simple language. Be helpful.""" | |
| context_parts = [ | |
| "=== VIDEO METADATA (always provided) ===", | |
| f"TITLE: {title or 'Not specified'}", | |
| f"SUBJECT: {subject or 'Not specified'}", | |
| f"LESSON/TOPIC: {lesson or 'Not specified'}", | |
| "", | |
| ] | |
| if has_file: | |
| context_parts.append("=== ASSOCIATED MATERIAL (parsed file content—use for summarize/explain) ===") | |
| context_parts.append(file_content) | |
| else: | |
| context_parts.append("=== ASSOCIATED MATERIAL ===") | |
| context_parts.append("(None—no file was uploaded or parsing failed.)") | |
| user_prompt = f"""VIDEO CONTEXT: | |
| {chr(10).join(context_parts)} | |
| STUDENT QUESTION: {question} | |
| TASK: Answer using the context above. For summarize/explain: use ASSOCIATED MATERIAL if present; otherwise use TITLE, SUBJECT, LESSON. Be specific.""" | |
| try: | |
| response = client.chat.completions.create( | |
| model="gpt-4o-mini", | |
| messages=[ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": user_prompt}, | |
| ], | |
| temperature=0.5, | |
| max_tokens=600, | |
| ) | |
| answer = response.choices[0].message.content.strip() | |
| return (answer, file_fetched) | |
| except Exception as e: | |
| err_msg = f"Sorry, I couldn't process your question. Please check that OPENAI_API_KEY is set correctly. Error: {str(e)}" | |
| return (err_msg, False) | |