Spaces:
Build error
Build error
| import os | |
| import textwrap | |
| import google.generativeai as genai | |
| from flask import Flask, request, jsonify, render_template_string | |
| # ========================================== | |
| # CONFIGURATION & SETUP | |
| # ========================================== | |
| app = Flask(__name__) | |
| # Configure the Google Gemini API | |
| # It looks for the GEMINI_API_KEY environment variable | |
| API_KEY = os.getenv("GEMINI_API_KEY") | |
| if not API_KEY: | |
| print("Warning: GEMINI_API_KEY environment variable not set.") | |
| print("Please set it in your Hugging Face Space secrets or Docker environment.") | |
| genai.configure(api_key=API_KEY) | |
| # ========================================== | |
| # FRONTEND TEMPLATE (HTML/CSS/JS) | |
| # ========================================== | |
| HTML_TEMPLATE = """ | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Deep Research with Google Gemini</title> | |
| <style> | |
| :root { | |
| --primary: #2563eb; | |
| --primary-hover: #1d4ed8; | |
| --bg: #f8fafc; | |
| --card-bg: #ffffff; | |
| --text: #1e293b; | |
| --text-light: #64748b; | |
| --border: #e2e8f0; | |
| } | |
| body { | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | |
| background-color: var(--bg); | |
| color: var(--text); | |
| margin: 0; | |
| padding: 0; | |
| line-height: 1.6; | |
| } | |
| .container { | |
| max-width: 800px; | |
| margin: 0 auto; | |
| padding: 2rem 1rem; | |
| } | |
| /* Header & Branding */ | |
| header { | |
| text-align: center; | |
| margin-bottom: 3rem; | |
| } | |
| h1 { | |
| font-size: 2.5rem; | |
| font-weight: 800; | |
| margin-bottom: 0.5rem; | |
| background: linear-gradient(135deg, #2563eb, #10b981); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .subtitle { | |
| color: var(--text-light); | |
| font-size: 1.1rem; | |
| } | |
| .anycoder-link { | |
| display: inline-block; | |
| margin-top: 1rem; | |
| font-size: 0.9rem; | |
| color: var(--text-light); | |
| text-decoration: none; | |
| border: 1px solid var(--border); | |
| padding: 0.25rem 0.75rem; | |
| border-radius: 999px; | |
| transition: all 0.2s; | |
| } | |
| .anycoder-link:hover { | |
| background-color: var(--card-bg); | |
| color: var(--text); | |
| border-color: var(--text); | |
| } | |
| /* Main Interface */ | |
| .search-box { | |
| background: var(--card-bg); | |
| padding: 2rem; | |
| border-radius: 1rem; | |
| box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1); | |
| border: 1px solid var(--border); | |
| margin-bottom: 2rem; | |
| } | |
| textarea { | |
| width: 100%; | |
| min-height: 120px; | |
| padding: 1rem; | |
| border: 1px solid var(--border); | |
| border-radius: 0.5rem; | |
| font-size: 1rem; | |
| font-family: inherit; | |
| resize: vertical; | |
| box-sizing: border-box; | |
| margin-bottom: 1rem; | |
| transition: border-color 0.2s; | |
| } | |
| textarea:focus { | |
| outline: none; | |
| border-color: var(--primary); | |
| box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1); | |
| } | |
| .btn { | |
| background-color: var(--primary); | |
| color: white; | |
| border: none; | |
| padding: 0.75rem 1.5rem; | |
| font-size: 1rem; | |
| font-weight: 600; | |
| border-radius: 0.5rem; | |
| cursor: pointer; | |
| transition: background-color 0.2s; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.5rem; | |
| } | |
| .btn:hover { | |
| background-color: var(--primary-hover); | |
| } | |
| .btn:disabled { | |
| background-color: var(--text-light); | |
| cursor: not-allowed; | |
| } | |
| /* Results Area */ | |
| .results { | |
| background: var(--card-bg); | |
| border-radius: 1rem; | |
| padding: 2rem; | |
| border: 1px solid var(--border); | |
| min-height: 200px; | |
| white-space: pre-wrap; | |
| display: none; /* Hidden by default */ | |
| } | |
| .results.active { | |
| display: block; | |
| animation: fadeIn 0.5s ease-out; | |
| } | |
| .results h2 { | |
| margin-top: 0; | |
| font-size: 1.5rem; | |
| } | |
| .error-msg { | |
| color: #ef4444; | |
| background-color: #fef2f2; | |
| padding: 1rem; | |
| border-radius: 0.5rem; | |
| margin-top: 1rem; | |
| border: 1px solid #fecaca; | |
| display: none; | |
| } | |
| /* Loading Spinner */ | |
| .spinner { | |
| width: 20px; | |
| height: 20px; | |
| border: 3px solid rgba(255,255,255,0.3); | |
| border-radius: 50%; | |
| border-top-color: white; | |
| animation: spin 1s ease-in-out infinite; | |
| display: none; | |
| } | |
| .btn.loading .spinner { display: block; } | |
| .btn.loading span { display: none; } | |
| @keyframes spin { to { transform: rotate(360deg); } } | |
| @keyframes fadeIn { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } | |
| /* Markdown-like styling for output */ | |
| .markdown-content h1, .markdown-content h2, .markdown-content h3 { margin-top: 1.5em; margin-bottom: 0.5em; color: #111; } | |
| .markdown-content p { margin-bottom: 1em; } | |
| .markdown-content ul, .markdown-content ol { margin-bottom: 1em; padding-left: 2em; } | |
| .markdown-content li { margin-bottom: 0.25em; } | |
| .markdown-content code { background: #f1f5f9; padding: 0.2em 0.4em; border-radius: 3px; font-family: monospace; font-size: 0.9em; } | |
| .markdown-content blockquote { border-left: 4px solid var(--border); padding-left: 1em; color: var(--text-light); margin: 1em 0; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <header> | |
| <h1>🧠 Deep Research</h1> | |
| <div class="subtitle">Powered by Google Gemini</div> | |
| <!-- CRITICAL: Built with anycoder link --> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link"> | |
| Built with anycoder | |
| </a> | |
| </header> | |
| <main> | |
| <div class="search-box"> | |
| <textarea id="topic" placeholder="Enter a research topic (e.g., 'The impact of AI on renewable energy efficiency')..."></textarea> | |
| <div style="display: flex; justify-content: flex-end;"> | |
| <button id="researchBtn" class="btn" onclick="startResearch()"> | |
| <span>Generate Report</span> | |
| <div class="spinner"></div> | |
| </button> | |
| </div> | |
| <div id="errorMsg" class="error-msg"></div> | |
| </div> | |
| <div id="resultsArea" class="results"> | |
| <div id="outputContent" class="markdown-content"></div> | |
| </div> | |
| </main> | |
| </div> | |
| <script> | |
| async function startResearch() { | |
| const topic = document.getElementById('topic').value.trim(); | |
| const btn = document.getElementById('researchBtn'); | |
| const resultsArea = document.getElementById('resultsArea'); | |
| const outputContent = document.getElementById('outputContent'); | |
| const errorMsg = document.getElementById('errorMsg'); | |
| if (!topic) { | |
| showError("Please enter a topic first."); | |
| return; | |
| } | |
| // Reset UI | |
| errorMsg.style.display = 'none'; | |
| resultsArea.classList.remove('active'); | |
| btn.classList.add('loading'); | |
| btn.disabled = true; | |
| outputContent.innerHTML = '<p>Analyzing topic and gathering data...</p>'; | |
| resultsArea.classList.add('active'); | |
| try { | |
| const response = await fetch('/api/research', { | |
| method: 'POST', | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: JSON.stringify({ topic: topic }) | |
| }); | |
| const data = await response.json(); | |
| if (!response.ok) { | |
| throw new Error(data.error || 'Failed to generate research'); | |
| } | |
| // Simple formatting for the output (converting newlines to breaks) | |
| // In a production app, use a library like marked.js | |
| const formattedText = data.report | |
| .replace(/^### (.*$)/gim, '<h3>$1</h3>') | |
| .replace(/^## (.*$)/gim, '<h2>$1</h2>') | |
| .replace(/^# (.*$)/gim, '<h1>$1</h1>') | |
| .replace(/\*\*(.*)\*\*/gim, '<b>$1</b>') | |
| .replace(/\n/gim, '<br>'); | |
| outputContent.innerHTML = formattedText; | |
| } catch (err) { | |
| showError(err.message); | |
| resultsArea.classList.remove('active'); | |
| } finally { | |
| btn.classList.remove('loading'); | |
| btn.disabled = false; | |
| } | |
| } | |
| function showError(msg) { | |
| const el = document.getElementById('errorMsg'); | |
| el.textContent = msg; | |
| el.style.display = 'block'; | |
| } | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| # ========================================== | |
| # BACKEND LOGIC | |
| # ========================================== | |
| def get_gemini_response(topic): | |
| """ | |
| Interacts with Google Gemini API to generate a deep research report. | |
| """ | |
| try: | |
| model = genai.GenerativeModel('gemini-pro') | |
| prompt = textwrap.dedent(f""" | |
| Act as an expert researcher. Conduct a deep dive into the following topic: "{topic}". | |
| Your report should follow this structure: | |
| 1. **Executive Summary**: A brief overview of the topic. | |
| 2. **Key Concepts**: Define the most important terms and ideas. | |
| 3. **Historical Context**: How did we get here? (if applicable). | |
| 4. **Current State of Affairs**: What is happening right now? | |
| 5. **Challenges & Controversies**: What are the debated points? | |
| 6. **Future Outlook**: Predictions and trends. | |
| Use Markdown formatting (headers, bolding, lists) to make the report readable. | |
| Be thorough, objective, and detailed. | |
| """) | |
| response = model.generate_content(prompt) | |
| return response.text | |
| except Exception as e: | |
| print(f"Error calling Gemini API: {e}") | |
| raise Exception(f"AI Error: {str(e)}") | |
| # ========================================== | |
| # FLASK ROUTES | |
| # ========================================== | |
| def home(): | |
| """Serves the frontend HTML.""" | |
| return render_template_string(HTML_TEMPLATE) | |
| def research(): | |
| """API endpoint to handle research requests.""" | |
| data = request.get_json() | |
| if not data or 'topic' not in data: | |
| return jsonify({"error": "No topic provided"}), 400 | |
| if not API_KEY: | |
| return jsonify({"error": "Server misconfiguration: GEMINI_API_KEY is missing."}), 500 | |
| topic = data['topic'] | |
| try: | |
| report = get_gemini_response(topic) | |
| return jsonify({"report": report}) | |
| except Exception as e: | |
| return jsonify({"error": str(e)}), 500 | |
| # ========================================== | |
| # MAIN ENTRY POINT | |
| # ========================================== | |
| if __name__ == '__main__': | |
| # Run the app on 0.0.0.0 so it is accessible externally in Docker | |
| port = int(os.environ.get('PORT', 7860)) | |
| app.run(host='0.0.0.0', port=port) |