import gradio as gr import requests from bs4 import BeautifulSoup from nltk import download, sent_tokenize import google.generativeai as genai import os import re import tempfile import asyncio # Download NLTK data download('punkt') download('punkt_tab') # Configure Gemini API using environment variable api_key = os.environ.get("GEMINI_API_KEY") if not api_key: raise ValueError("GEMINI_API_KEY not found in environment variables. Please set it in your environment.") genai.configure(api_key=api_key) # Use gemini-1.5-flash for faster and more accessible text analysis try: model = genai.GenerativeModel('gemini-1.5-flash') except Exception as e: # Fallback: List available models if the specified model is not found print(f"Error initializing model: {str(e)}") print("Available models:") for m in genai.list_models(): print(m.name) raise ValueError("Failed to initialize gemini-1.5-flash. Check available models above and update the model name.") # Prompt for Gemini to analyze text with specified output format PROMPT = """ You are an AI content reviewer. Analyze the provided text for the following: 1. *Grammar Issues*: Identify and suggest corrections for grammatical errors. 2. *Legal Policy Violations*: Flag content that may violate common legal policies (e.g., copyright infringement, defamation, incitement to violence). 3. *Crude/Abusive Language*: Detect crude, offensive, or abusive language. 4. *Sensitive Topics*: Identify content related to sensitive topics such as racism, gender bias, or other forms of discrimination. Return the results in the following markdown format: # Blog Review Report ## Grammar Corrections 1. [Heading of issue] - CONTENT: [Exact line or part of text with the issue] - SUGGESTION: [Suggested correction] - ISSUE: [Description of the issue] 2. [Heading of next issue] - CONTENT: [Exact line or part of text with the issue] - SUGGESTION: [Suggested correction] - ISSUE: [Description of the issue] [Continue numbering for additional issues or state "None detected"] ## Legal Policy Violations - CONTENT: [Exact line or part of text with the issue] SUGGESTION: [Suggested action or correction] ISSUE: [Description of the legal violation] [Or state "None detected"] ## Crude/Abusive Language - [List instances of crude or abusive language or "None detected"] ## Sensitive Topics - [List instances of sensitive topics or "None detected"] For each issue, provide the exact text, a suggested correction or action, and a concise explanation. Be precise and ensure the output strictly follows the specified format. """ async def fetch_url_content(url): try: response = requests.get(url, timeout=10) response.raise_for_status() soup = BeautifulSoup(response.text, 'html.parser') # Extract text from common content tags content = ' '.join([p.get_text(strip=True) for p in soup.find_all(['p', 'article', 'div'])]) return content if content else "No readable content found on the page." except Exception as e: return f"Error fetching URL: {str(e)}" async def review_blog(text_input, url_input): # Start loading effect immediately button_text = "Processing..." # Determine input type based on which field is populated if text_input and not url_input: input_type = "Text" input_text = text_input elif url_input and not text_input: input_type = "URL" input_text = url_input else: return "Review Blog", "Error: Please provide input in either the Text or URL tab, but not both.", gr.update(visible=False) # Handle empty input if not input_text: return "Review Blog", "Error: No input provided.", gr.update(visible=False) try: async with asyncio.timeout(30): # 30-second timeout for entire process # Handle URL input if input_type == "URL": button_text = "Fetching content..." input_text = await fetch_url_content(input_text) if input_text.startswith("Error"): return "Review Blog", input_text, gr.update(visible=False) # Tokenize input for analysis sentences = sent_tokenize(input_text) analysis_text = "\n".join(sentences) # Update button for API call button_text = "Generating report..." try: response = await asyncio.to_thread(model.generate_content, PROMPT + "\n\nText to analyze:\n" + analysis_text) report = response.text.strip() # Ensure the response is markdown by removing any code fences report = re.sub(r'^markdown\n|$', '', report, flags=re.MULTILINE) except Exception as e: report = f"Error analyzing content with Gemini: {str(e)}. Please check your API key, network connection, or model availability." # Fallback: List available models for debugging print("Available models:") for m in genai.list_models(): print(m.name) return "Review Blog", report, gr.update(visible=False) # Create a temporary file to store the report try: with tempfile.NamedTemporaryFile(mode='w', suffix='.md', delete=False, encoding='utf-8') as temp_file: temp_file.write(report) temp_file_path = temp_file.name # Add note to scroll to report report = f"**Report generated, please scroll down to view.**\n\n{report}" return "Review Blog", report, gr.update(visible=True, value=temp_file_path) except Exception as e: return "Review Blog", f"Error creating temporary file: {str(e)}", gr.update(visible=False) except asyncio.TimeoutError: return "Timeout", "Error: Process timed out after 30 seconds.", gr.update(visible=False) # Custom CSS for hover effect, loading state, and Inter font custom_css = """ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap'); .gradio-container { font-family: 'Inter', sans-serif !important; background-color: #f9f9f9; } .review-btn { transition: all 0.3s ease; font-weight: 600; font-size: 16px; background-color: #1f2937; color: white; border-radius: 12px; padding: 12px 24px; margin-top: 10px; } .review-btn:hover { background-color: #10b981; transform: scale(1.03); } .review-btn:disabled { opacity: 0.6; cursor: not-allowed; } .review-btn:disabled::before { content: ''; display: inline-block; width: 16px; height: 16px; border: 3px solid #fff; border-radius: 50%; border-top-color: transparent; animation: spin 1s linear infinite; margin-right: 8px; vertical-align: middle; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } input, textarea { font-family: 'Inter', sans-serif; font-size: 15px; padding: 10px; border-radius: 8px; } """ with gr.Blocks(theme=gr.themes.Base(), css=custom_css) as demo: gr.Markdown("## ๐ AI Blog Reviewer", elem_id="title") gr.Markdown( """