seifataa/Flashcards
0
1import os2import time3import json4from pathlib import Path5from flask import Flask, request, jsonify6from docx import Document7from pdfminer.high_level import extract_text as extract_pdf8import google.generativeai as genai9 10# ------------------------------11# Configure Gemini API keys12# ------------------------------13# Space secrets: GOOGLE_API_KEY_1 to GOOGLE_API_KEY_614API_KEYS = [15 os.getenv(f"GOOGLE_API_KEY_{i}") for i in range(1, 9)16]17if not any(API_KEYS):18 raise ValueError("Please set at least one GOOGLE_API_KEY_(1-6) secret in the Space settings.")19 20# Round-robin key tracker21current_key_index = 022MODEL_NAME = "gemini-flash-lite-latest" # lighter model23 24def switch_key():25 global current_key_index26 current_key_index = (current_key_index + 1) % len(API_KEYS)27 genai.configure(api_key=API_KEYS[current_key_index])28 return API_KEYS[current_key_index]29 30# Initialize first key31genai.configure(api_key=API_KEYS[current_key_index])32gemini = genai.GenerativeModel(MODEL_NAME)33 34# ------------------------------35# Flask app setup36# ------------------------------37app = Flask(__name__)38 39# ------------------------------40# Helper functions41# ------------------------------42def extract_text(file_path):43 ext = os.path.splitext(file_path)[1].lower()44 if ext == ".pdf":45 return extract_pdf(file_path)46 elif ext == ".docx":47 doc = Document(file_path)48 return "\n".join([p.text for p in doc.paragraphs])49 else:50 try:51 with open(file_path, "r", encoding="utf-8") as f:52 return f.read()53 except Exception:54 return ""55 56def chunk_text(text, max_len=2500):57 chunks = []58 start = 059 while start < len(text):60 chunks.append(text[start:start+max_len])61 start += max_len62 return chunks63 64def safe_generate(prompt, retries=6):65 """Call Gemini API safely with multi-key rotation and retry on quota errors."""66 attempt = 067 while attempt < retries:68 try:69 resp = gemini.generate_content(prompt)70 if resp.text:71 return resp.text.strip()72 return ""73 except Exception as e:74 err = str(e).lower()75 if "quota" in err or "429" in err:76 print(f"⚠️ Quota exceeded on key #{current_key_index + 1}, switching key...")77 switch_key()78 time.sleep(5) # small delay before retry79 attempt += 180 else:81 raise82 return "⚠️ Could not generate flashcards due to API limits."83 84# ------------------------------85# Flashcard generation endpoint86# ------------------------------87@app.route("/generate", methods=["POST"])88def generate_flashcards():89 if "file" not in request.files:90 return jsonify({"error": "No file uploaded"}), 40091 92 file = request.files["file"]93 temp_path = f"/tmp/{file.filename}"94 file.save(temp_path)95 96 text = extract_text(temp_path)97 if not text or len(text.strip()) < 50:98 return jsonify({"error": "File too short or empty"}), 40099 100 chunks = chunk_text(text)101 all_flashcards = []102 103 for idx, chunk in enumerate(chunks, start=1):104 prompt = (105 "Generate 3 concise medical flashcards from this text. "106 "Respond ONLY with a JSON array. Format strictly as: "107 '[{"front": "Question", "back": "Answer"}], separated by a semi-colon. '108 "Do NOT add extra commentary or explanations.\n\n"109 f"Text:\n{chunk}"110 )111 112 print(f"🧩 Processing chunk {idx}/{len(chunks)}...")113 result = safe_generate(prompt)114 115 try:116 cards = json.loads(result)117 if isinstance(cards, list):118 all_flashcards.extend(cards)119 else:120 all_flashcards.append({"front": "Parsing Error", "back": result})121 except Exception:122 all_flashcards.append({"front": "Parsing Error", "back": result})123 124 return jsonify({"data": all_flashcards})125 126@app.route("/", methods=["GET"])127def root():128 return jsonify({"status": "running", "message": "Flashcard backend ready!"})129 130if __name__ == "__main__":131 app.run(host="0.0.0.0", port=7860)