from __future__ import annotations import gradio as gr from core.config import get_api_key, set_api_key, ENV_VAR_NAME, in_hf_spaces from core.pipeline import analyze_part1, analyze_part2 from core.logging_config import configure_logging import logging configure_logging() logger = logging.getLogger(__name__) session_key = gr.State("") def _effective_key(user_input_key: str | None) -> str | None: """Priority: user input (session) > env (local .env or Space Secret).""" k = (user_input_key or "").strip() if k: return k return get_api_key() def on_save_key_local(key: str): if in_hf_spaces(): return gr.update(value="**On Spaces, keys are not stored. Use the key box per session or set a Space Secret.**") set_api_key(key) return gr.update(value="**Saved!** Your API key is now in .env.") def on_store_key_session(key: str): session_key.value = (key or "").strip() return gr.update(value="**Saved for this session.**") def on_analyze_part1(image_path, essay, key_box): key = _effective_key(key_box or session_key.value) if not key: raise RuntimeError(f"Provide {ENV_VAR_NAME} in Settings.") if not image_path: raise ValueError("Please upload the Task 1 image/chart/graph.") if not essay or len(essay.strip()) < 30: raise ValueError("Please paste your full Task 1 response (≥30 chars).") logger.info("UI: analyze_part1 invoked") return analyze_part1(image_path, essay, api_key=key) def on_analyze_part2(question, essay, key_box): key = _effective_key(key_box or session_key.value) if not key: raise RuntimeError(f"Provide {ENV_VAR_NAME} in Settings.") if not question or len(question.strip()) < 10: raise ValueError("Please paste the Task 2 question.") if not essay or len(essay.strip()) < 30: raise ValueError("Please paste your full Task 2 essay (≥30 chars).") logger.info("UI: analyze_part2 invoked") return analyze_part2(question, essay, api_key=key) APP_CSS = """ #out1 h2, #out2 h2 { border-bottom: 1px solid #eaecef; padding-bottom: 2px; } #out1 blockquote, #out2 blockquote { border-left: 3px solid #ddd; padding: .5rem 1rem; color: #555; background: #fafafa; } #out1, #out2 { max-width: 900px; } #mahak-center { display: flex; justify-content: center; } """ with gr.Blocks(title="IELTS Writing Assistant", fill_height=True, css=APP_CSS) as demo: gr.Markdown("# IELTS Writing Assistant.") gr.Markdown("---") gr.Markdown( """ **How to use (and privacy):** Go to the **Settings** tab → paste your **Google API key** from **Google AI Studio** → click **Save Session Key**. The key is stored **Temporarily** in that session (in `.env`) and is **not shared** with anyone else (including me). **Task 1:** upload the image/chart and paste your full response. **Task 2:** paste the question and your full response. **About the grade:** expect about **±0.5 band** error. Please treat the **written feedback and tips** as more important than the numeric grade. """, line_breaks=True ) with gr.Tab("Task 1"): with gr.Row(): image = gr.Image(type="filepath", label="Task 1 Image/Chart/Graph") essay1 = gr.Textbox(label="Your Task 1 Essay", lines=18) with gr.Row(): btn1 = gr.Button("Analyze", variant="primary") clear1 = gr.Button("Clear") out1 = gr.Markdown(label="Feedback") with gr.Tab("Task 2"): question = gr.Textbox(label="Task 2 Question", lines=4) essay2 = gr.Textbox(label="Your Task 2 Essay", lines=18) with gr.Row(): btn2 = gr.Button("Analyze", variant="primary") clear2 = gr.Button("Clear") out2 = gr.Markdown(label="Feedback") with gr.Tab("Settings"): gr.Markdown( f"Set your API key. Locally, you can save to `.env`. On Spaces, use **Session Key** or set a **Space Secret** `{ENV_VAR_NAME}`." ) key_box = gr.Textbox(label=f"{ENV_VAR_NAME}", type="password", placeholder="AIza... (your Google API key)") save_session = gr.Button("Save Session Key") status = gr.Markdown("") clear1.click(lambda: (None, "", ""), outputs=[image, essay1, out1]) clear2.click(lambda: ("", ""), outputs=[question, out2]) save_session.click(on_store_key_session, inputs=[key_box], outputs=[status]) btn1.click( fn=lambda: (gr.update(value="⏳ Analyzing Task 1…"), gr.update(interactive=False)), inputs=None, outputs=[out1, btn1], queue=False, ).then( fn=on_analyze_part1, inputs=[image, essay1], outputs=[out1], ).then( fn=lambda: gr.update(interactive=True), inputs=None, outputs=[btn1], ) btn2.click( fn=lambda: (gr.update(value="⏳ Analyzing Task 2…"), gr.update(interactive=False)), inputs=None, outputs=[out2, btn2], queue=False, ).then( fn=on_analyze_part2, inputs=[question, essay2], outputs=[out2], ).then( fn=lambda: gr.update(interactive=True), inputs=None, outputs=[btn2], ) gr.HTML(""" Donate to Mahak Charity """, elem_id="mahak-center") if __name__ == "__main__": demo.queue().launch()