Spaces:
Sleeping
Sleeping
| import gradio as gr | |
| import os | |
| import tempfile | |
| import datetime | |
| import time | |
| from google import genai | |
| from google.genai.errors import APIError | |
| from docx import Document | |
| from docx.shared import Pt | |
| from docx.enum.text import WD_ALIGN_PARAGRAPH | |
| # --- Configuration and Initialization --- | |
| try: | |
| API_KEY = os.getenv("GEMINI_API_KEY") | |
| if not API_KEY: | |
| raise ValueError("GEMINI_API_KEY environment variable is not set.") | |
| except ValueError as e: | |
| # This block handles local runs without the key, allowing the UI to load with a warning | |
| print(f"ERROR: {e}") | |
| API_KEY = None | |
| client = genai.Client(api_key=API_KEY) if API_KEY else None | |
| MODEL_NAME = "gemini-2.5-flash" | |
| # Utility lists for dropdowns | |
| MONTHS = [datetime.date(2000, m, 1).strftime('%B') for m in range(1, 13)] | |
| CURRENT_YEAR = datetime.datetime.now().year | |
| YEARS = [str(y) for y in range(CURRENT_YEAR, CURRENT_YEAR - 5, -1)] | |
| # --- STAGE 1: Find PYQ Topics --- | |
| def get_exam_topics(exam_name: str) -> str: | |
| """ | |
| Uses Google Search to find relevant PYQ themes/topics for the exam. | |
| """ | |
| if not client: return "" | |
| try: | |
| topic_prompt = f""" | |
| Find 8-10 of the most recurring and high-priority **General Awareness (GA) and Current Affairs themes** from the last 3 years of Previous Year Questions (PYQs) for the '{exam_name}' exam. | |
| Focus ONLY on themes relevant to a **fact-based, objective-type** test (like RRB NTPC, SSC). The themes should be about direct facts and recent developments. | |
| Example relevant themes: 'Railway Developments, New Government Appointments, Important National Schemes (Who/When/Where), Recent Awards/Honours, Scientific Discoveries (Who/What), Static GK hooks (e.g. National Parks in news)'. | |
| Output ONLY a comma-separated list of 5-8 key topics/subjects and nothing else. | |
| """ | |
| response = client.models.generate_content( | |
| model=MODEL_NAME, | |
| contents=topic_prompt, | |
| config={"tools": [{"google_search": {}}], "temperature": 0.1}, | |
| ) | |
| return response.text.strip().replace('\n', ', ').strip(', ') | |
| except Exception as e: | |
| print(f"Error finding exam topics: {e}") | |
| return "" | |
| # --- STAGE 2: Generate Current Affairs Informed by Topics (Student-Centric, Fact-Dense Prompt) --- | |
| def get_current_affairs_content(exam_name: str, topics: str, month: str, year: str) -> str: | |
| """ | |
| Calls the Gemini model with Google Search grounding to get structured current affairs. | |
| IMPROVED: Stronger demand for density, diversity, Static GK linkage, and structured as a study checklist. | |
| """ | |
| if not client: return f"❌ **ERROR**: Gemini API Key is not configured." | |
| time_frame = f"{month} {year}" | |
| topic_guidance = f"Prioritize current affairs that fall under these key themes: {topics}." if topics else "Focus on general high-priority current affairs." | |
| # CRITICAL INSTRUCTION: Enforces high density, non-redundancy, and student-friendly format. | |
| system_instruction = f""" | |
| You are a specialized AI content generator for **government exam MCQ revision**, focused on **factual, objective-type** questions. | |
| Your task: Generate a **diverse**, **non-redundant**, and **fact-dense** set of **current affairs** from **{time_frame}**, tailored to the **{exam_name}** exam. | |
| STRICT OUTPUT GUIDELINES: | |
| 1. Use **Google Search grounding** to ensure **100% factual accuracy** and **timeliness**. | |
| 2. Each fact/event must be presented as a **standalone, exam-ready revision point**, suitable for MCQs. | |
| 3. Format: Group under 5–6 broad categories (Level 1 headers like 'National Appointments', 'Schemes', etc.). | |
| 4. Each item under a category must include: | |
| - **Headline/Event** (short, specific) | |
| - **3–4 bulletproof factual sentences** (dense, no fluff; bold all keywords/numbers/entities) | |
| - A **Static GK Link** — one related factual anchor (e.g., capital, HQ, theme, location) | |
| DO NOT: | |
| - Invent facts or speculate. Only use verifiable public events. | |
| - Repeat events across categories or over-focus on one domain. | |
| - Add introductions, summaries, or editorial comments. | |
| Format all content in clean **Markdown**, using: | |
| - `#` for category | |
| - `##` for numbered facts | |
| - `**` for bolding important info | |
| - `**Static GK Link:**` for the revision hook | |
| {topic_guidance} | |
| Example output structure: | |
| # National Appointments & Honours | |
| ## 1. [New Appointment or Award Name] | |
| **XYZ** was appointed as the new **Chairman of ABC** on **June 3, 2024**. The appointment is for a **3-year tenure**. He replaces **PQR**. | |
| **Static GK Link:** HQ of ABC is in **New Delhi**. | |
| Only return Markdown-formatted content. No explanation or narrative. | |
| """ | |
| try: | |
| response_stream = client.models.generate_content_stream( | |
| model=MODEL_NAME, | |
| # Query is specific about the time frame and topics | |
| contents=f"Generate diverse, non-redundant, and fact-rich current affairs for the '{exam_name}' exam from {time_frame}. Focus on facts related to: {topics}", | |
| config={"tools": [{"google_search": {}}], "system_instruction": system_instruction, "temperature": 0.5}, | |
| ) | |
| full_text = "" | |
| for chunk in response_stream: | |
| if chunk.text: | |
| full_text += chunk.text | |
| return full_text | |
| except APIError as e: | |
| return f"❌ **API Error occurred**: {e}" | |
| except Exception as e: | |
| return f"❌ **An unexpected error occurred**: {e}" | |
| def create_docx_from_markdown(markdown_text: str, exam_name: str, run_date: datetime.datetime, month: str, year: str) -> str: | |
| """Parses the structured Markdown text and converts it into a formatted docx document.""" | |
| document = Document() | |
| title = document.add_heading(f"MCQ-Focused Current Affairs for {exam_name} ({month} {year})", 0) | |
| title.alignment = WD_ALIGN_PARAGRAPH.CENTER | |
| for run in title.runs: run.bold = True; run.font.size = Pt(20) | |
| document.add_paragraph(f"Generated by Gemini 2.5 Flash with Google Search Grounding | Report Date: {run_date.strftime('%B %d, %Y')}") | |
| document.add_paragraph('') | |
| for line in markdown_text.split('\n'): | |
| line = line.strip() | |
| if line.startswith('# '): document.add_heading(line.lstrip('# ').strip(), level=1) | |
| elif line.startswith('## '): document.add_heading(line.lstrip('## ').strip(), level=2) | |
| elif line: | |
| p = document.add_paragraph() | |
| parts = line.split('**') | |
| for i, part in enumerate(parts): | |
| p.add_run(part).bold = (i % 2 == 1) | |
| temp_dir = tempfile.gettempdir() | |
| file_path = os.path.join(temp_dir, f"{exam_name.replace(' ', '_')}_CA_{month}_{year}.docx") | |
| document.save(file_path) | |
| return file_path | |
| # --- Gradio Interface Function (Orchestrator) --- | |
| def generate_current_affairs(exam_name: str, month: str, year: str): | |
| """ | |
| Orchestrates the three stages (PYQ, CA Generation, DOCX) with live status updates. | |
| """ | |
| yield "💡 **Status:** Starting the two-stage search...", "", None | |
| if not exam_name or not month or not year: | |
| yield "❌ **Status:** Please ensure Exam Name, Month, and Year are all selected.", "", None | |
| return | |
| if not API_KEY: | |
| error_message = "❌ **Status:** API Key Error. The `GEMINI_API_KEY` is missing." | |
| yield error_message, "", None | |
| return | |
| # Stage 1: Find PYQ Topics | |
| status = f"🔍 **Status:** Stage 1/3: Finding relevant **PYQ themes** for **{exam_name}**... (Tool Call 1: Google Search)" | |
| yield status, "", None | |
| time.sleep(0.5) | |
| topics = get_exam_topics(exam_name) | |
| if topics: | |
| status += f"\n✅ **Stage 1 Complete:** Found topics: *{topics.replace(',', ' | ')}*." | |
| else: | |
| status += "\n⚠️ **Stage 1 Complete:** Could not find specific PYQ themes. Proceeding with general search." | |
| yield status, "", None | |
| # Stage 2: Generate Current Affairs Content | |
| status += f"\n\n🚀 **Status:** Stage 2/3: Generating **Categorized Current Affairs** for **{month} {year}**... (Tool Call: Google Search & Gemini)" | |
| yield status, "", None | |
| time.sleep(0.5) | |
| markdown_output = get_current_affairs_content(exam_name, topics, month, year) | |
| if markdown_output.startswith('❌'): | |
| final_status = status + "\n" + markdown_output.split(':')[0] | |
| yield final_status, markdown_output, None | |
| return | |
| # Step 3: Convert Markdown to Docx | |
| status += "\n\n📄 **Status:** Stage 3/3: Finalizing formatting and creating downloadable .docx file..." | |
| yield status, markdown_output, None | |
| time.sleep(0.5) | |
| run_date = datetime.datetime.now() | |
| docx_file_path = create_docx_from_markdown(markdown_output, exam_name, run_date, month, year) | |
| # Final successful return | |
| final_status = status.split("\n\n")[0] + "\n\n🎉 **Success!** Preview content below and download the file." | |
| yield final_status, markdown_output, docx_file_path | |
| # --- Gradio UI Definition --- | |
| def clear_all(): | |
| """Function to clear all output components and the input box.""" | |
| current_month = datetime.datetime.now().strftime('%B') | |
| current_year_str = str(datetime.datetime.now().year) | |
| return "Enter the exam name and click **Find** to start.", "", None, "RRB NTPC", current_month, current_year_str | |
| # Define the components | |
| input_exam_name = gr.Textbox( | |
| label="Government Exam Name", | |
| value="RRB NTPC", | |
| placeholder="e.g., UPSC Civil Services, SSC CGL, RRB NTPC" | |
| ) | |
| input_month = gr.Dropdown( | |
| label="Current Affairs Month", | |
| choices=MONTHS, | |
| value=datetime.datetime.now().strftime('%B'), | |
| allow_custom_value=False, | |
| scale=1 | |
| ) | |
| input_year = gr.Dropdown( | |
| label="Current Affairs Year", | |
| choices=YEARS, | |
| value=str(datetime.datetime.now().year), | |
| allow_custom_value=False, | |
| scale=1 | |
| ) | |
| find_button = gr.Button("Find Current Affairs", variant="primary", scale=2) | |
| clear_button = gr.Button("Clear All Outputs", scale=1) | |
| # Outputs (Note: 'scale' is intentionally omitted from these definitions to prevent TypeError) | |
| output_info = gr.Markdown(label="Process Status & Found Topics", value="Enter the exam name, select the month/year, and click **Find** to start.") | |
| output_markdown = gr.Markdown(label="Current Affairs Preview (Categorized, Highlighted)") | |
| output_file = gr.File(label="Download Formatted Word Document (.docx)", file_types=[".docx"]) | |
| # Define the Gradio Interface | |
| with gr.Blocks(title="Current Affairs Finder for Exam Preparation") as demo: | |
| gr.Markdown( | |
| """ | |
| # 🎓Agent 1x: Gemini 2.5 Flash – Your One-Stop Current Affairs Note-Maker | |
| **Goal:** Creates **Exam-Ready Study Notes** by linking Current Affairs to **Static GK** for a chosen month, perfectly formatted for printing. | |
| * **Stage 1 (Target):** Finds **PYQ-based key topics** . | |
| * **Stage 2 (Content):** Generates **time-specific facts** with **Static GK Links** . | |
| --- | |
| """ | |
| ) | |
| # Main Row for Inputs and Status | |
| with gr.Row(): | |
| # Left Column (Input and Controls - scale=1) | |
| with gr.Column(scale=1): | |
| input_exam_name.render() | |
| with gr.Row(): | |
| input_month.render() | |
| input_year.render() | |
| with gr.Row(): | |
| find_button.render() | |
| clear_button.render() | |
| # Right Column (Status/Progression - scale=3) | |
| with gr.Column(scale=3): | |
| output_info.render() | |
| # New Row for Preview and Download | |
| with gr.Row(): | |
| with gr.Column(): | |
| output_markdown.render() | |
| output_file.render() | |
| # --- Interaction Logic --- | |
| find_button.click( | |
| fn=generate_current_affairs, | |
| inputs=[input_exam_name, input_month, input_year], | |
| outputs=[output_info, output_markdown, output_file], | |
| ) | |
| clear_button.click( | |
| fn=clear_all, | |
| outputs=[output_info, output_markdown, output_file, input_exam_name, input_month, input_year] | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() |