Spaces:
Sleeping
Sleeping
| import os | |
| import shutil | |
| import tempfile | |
| from flask import Flask, request, jsonify, render_template_string | |
| import git | |
| import json | |
| app = Flask(__name__) | |
| # --- HTML Template remains embedded. No changes needed here. --- | |
| HTML_TEMPLATE = """ | |
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>GitHub README Generator</title> | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&display=swap'); | |
| body { font-family: 'Inter', sans-serif; background-color: #f0f2f5; color: #1c1e21; margin: 0; display: flex; justify-content: center; align-items: center; min-height: 100vh; padding: 20px; box-sizing: border-box; } | |
| .container { background-color: #ffffff; padding: 40px 50px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); width: 100%; max-width: 700px; text-align: center; } | |
| h1 { font-size: 2.2em; color: #1877f2; margin-bottom: 10px; } | |
| p { color: #606770; font-size: 1.1em; margin-bottom: 30px; } | |
| .input-group { display: flex; margin-bottom: 20px; } | |
| #repo-url { flex-grow: 1; padding: 15px; border: 1px solid #dddfe2; border-radius: 6px 0 0 6px; font-size: 1em; outline: none; min-width: 0; } | |
| #repo-url:focus { border-color: #1877f2; box-shadow: 0 0 0 2px rgba(24, 119, 242, 0.2); } | |
| button { padding: 15px 25px; border: none; background-color: #1877f2; color: white; font-size: 1em; font-weight: 600; border-radius: 0 6px 6px 0; cursor: pointer; transition: background-color 0.3s; } | |
| button:hover { background-color: #166fe5; } | |
| .loader { border: 4px solid #f3f3f3; border-top: 4px solid #1877f2; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 30px auto; display: none; } | |
| @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } | |
| #result-container { display: none; margin-top: 20px; } | |
| #result { margin-top: 10px; padding: 20px; background-color: #f7f7f7; border: 1px solid #dddfe2; border-radius: 6px; text-align: left; white-space: pre-wrap; font-family: 'Courier New', Courier, monospace; max-height: 400px; overflow-y: auto; word-wrap: break-word; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>GitHub README Generator ๐ป</h1> | |
| <p>Enter a public GitHub repository URL and let the AI agent generate a professional README.md for you.</p> | |
| <form id="repo-form"><div class="input-group"><input type="url" id="repo-url" placeholder="e.g., https://github.com/user/project" required><button type="submit">Generate</button></div></form> | |
| <div class="loader" id="loader"></div> | |
| <div id="result-container"><h2>Generated README.md:</h2><pre id="result"></pre></div> | |
| </div> | |
| <script> | |
| document.getElementById('repo-form').addEventListener('submit', async function(event) { | |
| event.preventDefault(); | |
| const url = document.getElementById('repo-url').value; | |
| const loader = document.getElementById('loader'); | |
| const resultContainer = document.getElementById('result-container'); | |
| const resultDiv = document.getElementById('result'); | |
| loader.style.display = 'block'; | |
| resultContainer.style.display = 'none'; | |
| resultDiv.textContent = ''; | |
| try { | |
| const response = await fetch('/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: url }) }); | |
| const data = await response.json(); | |
| if (response.ok) { resultDiv.textContent = data.readme; } else { resultDiv.textContent = 'Error: ' + data.error; } | |
| resultContainer.style.display = 'block'; | |
| } catch (error) { | |
| resultDiv.textContent = 'An unexpected error occurred: ' + error.toString(); | |
| resultContainer.style.display = 'block'; | |
| } finally { | |
| loader.style.display = 'none'; | |
| } | |
| }); | |
| </script> | |
| </body> | |
| </html> | |
| """ | |
| def analyze_repo(repo_path): | |
| """ | |
| Analyzes the cloned repository to gather information. | |
| This function now correctly receives the path to the project's root folder. | |
| """ | |
| # FIX: The project name is the name of the folder we are analyzing. | |
| project_name = os.path.basename(repo_path) | |
| language = "Not Detected" | |
| dependencies = [] | |
| install_command = "No specific install command found." | |
| run_command = "No specific run command found." | |
| purpose = f"A project named '{project_name}'." | |
| # --- Smarter Project Purpose Inference --- | |
| # Try to find a description from an existing README | |
| existing_readme_path = os.path.join(repo_path, 'README.md') | |
| if os.path.exists(existing_readme_path): | |
| with open(existing_readme_path, 'r', errors='ignore') as f: | |
| lines = f.readlines() | |
| # Often the first few lines after the title contain a description | |
| if len(lines) > 2: | |
| purpose = "".join(lines[1:3]).strip() # Grab the first two lines after title | |
| # --- Technology and Dependency Analysis --- | |
| if os.path.exists(os.path.join(repo_path, 'requirements.txt')): | |
| language = "Python" | |
| with open(os.path.join(repo_path, 'requirements.txt'), 'r') as f: | |
| dependencies = [line.strip() for line in f.readlines() if line.strip() and not line.startswith('#')] | |
| install_command = "pip install -r requirements.txt" | |
| if 'flask' in str(dependencies).lower(): | |
| run_command = "flask run" | |
| elif 'django' in str(dependencies).lower(): | |
| run_command = "python manage.py runserver" | |
| elif os.path.exists(os.path.join(repo_path, 'app.py')): | |
| run_command = "python app.py" | |
| elif os.path.exists(os.path.join(repo_path, 'main.py')): | |
| run_command = "python main.py" | |
| elif os.path.exists(os.path.join(repo_path, 'package.json')): | |
| language = "JavaScript (Node.js)" | |
| with open(os.path.join(repo_path, 'package.json'), 'r') as f: | |
| package_data = json.load(f) | |
| dependencies = list(package_data.get('dependencies', {}).keys()) | |
| if package_data.get('description'): | |
| purpose = package_data.get('description') | |
| install_command = "npm install" | |
| if package_data.get('scripts', {}).get('start'): | |
| run_command = "npm start" | |
| else: | |
| run_command = "node index.js # (or appropriate main file)" | |
| return { | |
| "project_name": project_name, "language": language, "dependencies": dependencies, | |
| "install_command": install_command, "run_command": run_command, "purpose": purpose | |
| } | |
| def generate_readme_content(analysis): | |
| """Generates the README.md content from the analysis results.""" | |
| readme = f"# {analysis['project_name'].replace('-', ' ').title()}\n\n" | |
| readme += f"## ๐ค About This Project\n\n{analysis['purpose']}\n\n" | |
| readme += f"This project appears to be written in **{analysis['language']}**.\n\n" | |
| readme += "## ๐ Getting Started\n\n" | |
| readme += "### Installation\n\n1. **Clone the repository:**\n ```sh\n git clone <your_repository_url>\n ```\n\n" | |
| readme += f"2. **Navigate into the directory and install dependencies:**\n ```sh\n cd {analysis['project_name']}\n {analysis['install_command']}\n ```\n\n" | |
| if analysis['dependencies']: | |
| readme += "### Key Dependencies\n" | |
| for dep in analysis['dependencies'][:5]: # Show first 5 dependencies | |
| readme += f"- `{dep}`\n" | |
| readme += "\n" | |
| readme += "### Usage\n\nTo run the application, execute the following command from the project root:\n" | |
| readme += f"```sh\n{analysis['run_command']}\n```\n\n" | |
| readme += "---\n*This README was automatically generated by an AI agent.*" | |
| return readme | |
| def index(): | |
| return render_template_string(HTML_TEMPLATE) | |
| def generate(): | |
| repo_url = request.get_json().get('url') | |
| if not repo_url or not repo_url.startswith("https://github.com/"): | |
| return jsonify({"error": "A valid GitHub repository URL is required."}), 400 | |
| temp_dir = tempfile.mkdtemp() | |
| try: | |
| # --- Agent Tool 1: Git Clone --- | |
| print(f"Cloning repository: {repo_url}") | |
| git.Repo.clone_from(repo_url, temp_dir) | |
| # --- FIX: Find the actual project folder inside the temp directory --- | |
| cloned_folders = os.listdir(temp_dir) | |
| if not cloned_folders: | |
| raise Exception("Cloning seems to have failed, the directory is empty.") | |
| # The actual repo path is inside the temp_dir | |
| repo_root_path = os.path.join(temp_dir, cloned_folders[0]) | |
| print(f"Analysis target found at: {repo_root_path}") | |
| # --- Agent Tool 2: File Analysis (now on the correct path) --- | |
| analysis_result = analyze_repo(repo_root_path) | |
| # --- Agent Step 3: Write README --- | |
| readme_content = generate_readme_content(analysis_result) | |
| return jsonify({"readme": readme_content}) | |
| except git.exc.GitCommandError as e: | |
| return jsonify({"error": f"Failed to clone. Is the URL correct and public? Details: {e}"}), 500 | |
| except Exception as e: | |
| return jsonify({"error": f"An unexpected error occurred: {str(e)}"}), 500 | |
| finally: | |
| # --- Cleanup --- | |
| print(f"Cleaning up temporary directory: {temp_dir}") | |
| shutil.rmtree(temp_dir) | |
| if __name__ == '__main__': | |
| app.run(host='0.0.0.0', port=int(os.environ.get("PORT", 7860))) |