import gradio as gr import requests import json import os # IMPORTANT: For deployment on Hugging Face Spaces, # store your Google API Key as a Space Secret named 'GOOGLE_API_KEY'. # Do NOT hardcode your API key directly in this file. GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY", "") # Base function to call the Gemini API def call_gemini_api(prompt): """ Makes an API call to the Gemini 2.0 Flash model. """ if not GOOGLE_API_KEY: raise ValueError("Google API Key is not configured. Please set it as a Space Secret or environment variable.") payload = { "contents": [ { "role": "user", "parts": [{"text": prompt}] } ] } api_url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key={GOOGLE_API_KEY}" try: response = requests.post( api_url, headers={"Content-Type": "application/json"}, data=json.dumps(payload) ) response.raise_for_status() # Raise an HTTPError for bad responses (4xx or 5xx) result = response.json() if result.get("candidates") and len(result["candidates"]) > 0 and \ result["candidates"][0].get("content") and result["candidates"][0]["content"].get("parts") and \ len(result["candidates"][0]["content"]["parts"]) > 0: return result["candidates"][0]["content"]["parts"][0]["text"] else: return "No content generated. The AI might not have understood the request or returned an empty response." except requests.exceptions.RequestException as e: return f"API request failed: {e}. Please check your internet connection or API key." except json.JSONDecodeError: return "Failed to parse API response. Invalid JSON received." except Exception as e: return f"An unexpected error occurred: {e}" # --- Core Content Generation Functions --- def generate_personalized_content(topic, content_type, difficulty, language, persona): """ Generates personalized notes, quizzes, or explanations with specified difficulty, language, and persona. """ if not topic.strip(): return "Please enter a topic or question." persona_instruction = "" if persona == "Like a Professor": persona_instruction = "Adopt a formal, academic, and highly informative tone." elif persona == "Like a Storyteller": persona_instruction = "Explain it as if you are telling a captivating story, using engaging narrative." elif persona == "Like a Friend": persona_instruction = "Use a casual, friendly, and encouraging tone, simplifying complex ideas." elif persona == "Like a Scientist": persona_instruction = "Provide a precise, factual, and technical explanation, using scientific terminology where appropriate." elif persona == "Explain Like I'm 5": persona_instruction = "Explain it in extremely simple terms, as if to a 5-year-old, using basic vocabulary and analogies." prompt = f"As an expert educational assistant, {persona_instruction} generate {content_type} on the following topic: '{topic}' for a {difficulty} level learner. Provide the output in {language}." if content_type == 'notes': prompt += "\n\nProvide comprehensive, well-structured notes with key concepts, definitions, and examples. Use clear headings and bullet points. Format the output in Markdown for readability." elif content_type == 'quiz': prompt += "\n\nGenerate a short, challenging quiz (3-5 questions) with multiple-choice options and correct answers clearly marked. Include a mix of conceptual and application-based questions. Clearly label questions and answers (e.g., Q1:, A:, B:, C:, D:, Correct Answer:)." elif content_type == 'explanation': prompt += "\n\nProvide a detailed and easy-to-understand explanation, breaking down complex ideas into simpler terms. Use analogies if helpful. Format the output in Markdown for readability." return call_gemini_api(prompt) def summarize_text_content(text_to_summarize, summary_length, language): """ Summarizes provided text in a specified language. """ if not text_to_summarize.strip(): return "Please paste some text to summarize." prompt = f"Summarize the following text. Make the summary {summary_length} length. Focus on key points and main ideas. Provide the summary in {language}. Text:\n\n{text_to_summarize}" return call_gemini_api(prompt) def generate_flashcards_content(flashcard_topic, language): """ Generates flashcards (Q&A pairs) for a given topic in a specified language. """ if not flashcard_topic.strip(): return "Please enter a topic for flashcards." prompt = f"Generate 5-7 distinct question and answer flashcards for the topic: '{flashcard_topic}'. Format each flashcard as 'Q: [Question]\nA: [Answer]'. Use clear and concise language. Provide the flashcards in {language}." return call_gemini_api(prompt) def ai_tutor_chat(message, history, language): """ Handles conversational interaction for the AI Tutor in a specified language. """ chat_history_gemini = [] for human, ai in history: chat_history_gemini.append({"role": "user", "parts": [{"text": human}]}) chat_history_gemini.append({"role": "model", "parts": [{"text": ai}]}) chat_history_gemini.append({"role": "user", "parts": [{"text": message}]}) system_instruction = f"You are a friendly and knowledgeable AI tutor. Your goal is to help users understand concepts, answer their questions, and guide them in their learning journey. Keep your responses concise and helpful. Respond in {language}." full_prompt_with_history = system_instruction + "\n\n" for entry in chat_history_gemini: if entry["role"] == "user": full_prompt_with_history += f"User: {entry['parts'][0]['text']}\n" elif entry["role"] == "model": full_prompt_with_history += f"Tutor: {entry['parts'][0]['text']}\n" full_prompt_with_history += f"User: {message}\nTutor:" try: response_text = call_gemini_api(full_prompt_with_history) return response_text except Exception as e: return f"Error in chat: {e}" def generate_concept_map(topic, language): """ Generates a concept map in Mermaid.js syntax for a given topic in a specified language. """ if not topic.strip(): return "Please enter a topic to generate a concept map." prompt = f"""Generate a concept map for the topic '{topic}' using Mermaid.js graph syntax. Focus on 5-10 key concepts and their direct relationships. Use `graph TD` for a top-down flow. Nodes should be defined as A[Concept Name] or B(Concept Name). Connections should be A --> B or A -- "Relationship" --> B. Ensure the output is ONLY the Mermaid.js code block, starting and ending with ```mermaid and ```. DO NOT include any introductory or concluding text outside the code block. Provide the concept names and relationships in {language}. Example format: ```mermaid graph TD A[Main Concept] --> B(Sub-concept 1) A --> C(Sub-concept 2) B --> D{{Detail 1}} C --> E[Detail 2] ``` """ try: raw_mermaid_code = call_gemini_api(prompt) if "```mermaid" not in raw_mermaid_code: rendered_output = f"```mermaid\n{raw_mermaid_code}\n```" else: rendered_output = raw_mermaid_code return rendered_output except Exception as e: error_msg = f"An error occurred during concept map generation: {e}" return error_msg def generate_practice_problems(subject_topic, language): """ Generates practice problems with solutions for a given subject/topic in a specified language. """ if not subject_topic.strip(): return "Please enter a subject or topic for practice problems." prompt = f"""Generate 3-5 practice problems for the subject/topic: '{subject_topic}'. For each problem, provide a clear problem statement and a step-by-step solution. Format clearly with headings for Problem and Solution. Use Markdown. Provide the problems and solutions in {language}. Example: ## Problem 1 [Problem statement] ### Solution [Step-by-step solution] """ return call_gemini_api(prompt) def generate_essay_outline(topic, key_points, language): """ Generates an essay/report outline for a given topic and optional key points. """ if not topic.strip(): return "Please enter a topic for the essay outline." prompt = f"Generate a detailed essay or report outline for the topic: '{topic}'. " if key_points.strip(): prompt += f"Include the following key points: {key_points}. " prompt += f"Structure it with an Introduction, Body Paragraphs (with main ideas and supporting details), and Conclusion. Provide the outline in {language}. Use Markdown for formatting." return call_gemini_api(prompt) def generate_vocabulary(topic_or_text, language): """ Extracts key vocabulary from a topic or text and provides definitions. """ if not topic_or_text.strip(): return "Please enter a topic or paste text to extract vocabulary." prompt = f"Extract 5-10 key vocabulary terms from the following topic/text and provide a concise definition for each. Format as a list: '- Term: Definition'. Provide the terms and definitions in {language}. Topic/Text:\n\n{topic_or_text}" return call_gemini_api(prompt) # --- Clear functions for each tab --- def clear_personalized_content(): return "", "notes", "Intermediate", "Default", "" # topic, content_type, difficulty, persona, output def clear_summarizer(): return "", "medium", "" # text_to_summarize, summary_length, output def clear_flashcards(): return "", "" # flashcard_topic, output # AI Tutor Chat has its own clear button # def clear_ai_tutor_chat(): # return [] # history def clear_concept_map(): return "", "" # topic, rendered_output def clear_practice_problems(): return "", "" # subject_topic, output def clear_essay_outline(): return "", "", "" # topic, key_points, output def clear_vocabulary(): return "", "" # topic_or_text, output # --- Gradio Interface Definition --- # Custom CSS for a more attractive UI custom_css = """ body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #e0f2f7 0%, #c8e6c9 100%); /* Light, calming gradient */ } h1 { color: #3f51b5 !important; /* Deeper indigo for main title */ text-shadow: 1px 1px 2px rgba(0,0,0,0.1); font-size: 2.8rem !important; /* Slightly larger */ margin-bottom: 10px; /* Reduced margin */ } h3 { /* For tab headings */ color: #4CAF50 !important; /* Green for section titles */ font-size: 1.8rem !important; margin-bottom: 1.5rem; border-bottom: 2px solid #81C784; /* Subtle underline */ padding-bottom: 0.5rem; } p { color: #555 !important; font-size: 1.1rem !important; } .gradio-container { border-radius: 15px !important; box-shadow: 0 10px 30px rgba(0,0,0,0.15) !important; /* Stronger shadow */ overflow: hidden; /* Ensures rounded corners apply to inner content */ } .gr-button { background-color: #4CAF50 !important; /* Green button */ color: white !important; border: none !important; border-radius: 8px !important; padding: 12px 25px !important; font-size: 1.1rem !important; transition: all 0.3s ease !important; box-shadow: 0 4px 8px rgba(0, 150, 0, 0.2); } .gr-button:hover { background-color: #66BB6A !important; /* Lighter green on hover */ transform: translateY(-2px); /* Subtle lift effect */ box-shadow: 0 6px 12px rgba(0, 150, 0, 0.3); } .gr-textbox, .gr-dropdown, .gr-radio { border-radius: 8px !important; border: 1px solid #BDBDBD !important; /* Lighter border */ box-shadow: inset 0 1px 3px rgba(0,0,0,0.05); /* Inner shadow for depth */ } .gr-label { font-weight: bold !important; color: #424242 !important; } /* Style for the tabs */ .tabs { border-radius: 10px; background-color: #f5f5f5; /* Light background for tabs container */ padding: 10px; box-shadow: inset 0 0 5px rgba(0,0,0,0.05); } .tab-nav button { border-radius: 8px 8px 0 0 !important; font-weight: 600 !important; color: #757575 !important; background-color: #e0e0e0 !important; transition: all 0.2s ease; } .tab-nav button.selected { background-color: #ffffff !important; /* White for selected tab */ color: #4F46E5 !important; /* Primary color for selected tab text */ border-bottom: 3px solid #4F46E5 !important; /* Underline for selected tab */ } .gradio-container .prose { /* Target markdown output */ line-height: 1.7; font-size: 1.05rem; color: #333; } .gradio-container .prose h2 { color: #3f51b5 !important; border-bottom: 1px dashed #9fa8da; padding-bottom: 5px; margin-top: 1.5em; } .gradio-container .prose h3 { color: #4CAF50 !important; margin-top: 1.2em; } /* Specific style for clear buttons */ .clear-button { background-color: #f44336 !important; /* Red for clear */ box-shadow: 0 4px 8px rgba(244, 67, 54, 0.2); } .clear-button:hover { background-color: #ef5350 !important; box-shadow: 0 6px 12px rgba(244, 67, 54, 0.3); } /* Style for the main description below the title */ .gradio-container > div > p:nth-child(2) { /* Targeting the second
which is the description */ margin-bottom: 2.5rem; /* More space below description */ } """ with gr.Blocks(theme=gr.themes.Monochrome(), css=custom_css) as demo: gr.Markdown( """
Your intelligent companion for tailored study materials, interactive tutoring, and advanced learning aids.
""" ) # Define common language dropdown for multiple tabs language_dropdown = gr.Dropdown( ["English", "Urdu", "Spanish", "French", "German", "Chinese"], label="Select Output Language:", value="English", interactive=True ) with gr.Tabs(): # Tab 1: Personalized Content (Notes, Quiz, Explanation) with gr.TabItem("Personalized Content"): gr.Markdown("