├── package.json ├── .gitignore ├── frontend ├── postcss.config.js ├── vite.config.js ├── src │ ├── index.css │ ├── main.jsx │ ├── App.css │ ├── assets │ │ └── react.svg │ └── App.jsx ├── .gitignore ├── README.md ├── package.json ├── eslint.config.js ├── public │ └── vite.svg ├── tailwind.config.js └── index.html ├── backend ├── requirements.txt ├── __init__.py ├── install_repomix.sh ├── prompts.py └── main.py ├── LICENSE └── README.md /package.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | __pycache__ 4 | .env -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi 2 | python-dotenv 3 | pydantic 4 | langchain-google-genai 5 | uvicorn 6 | requests 7 | python-multipart 8 | langchain -------------------------------------------------------------------------------- /frontend/vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vite.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /frontend/src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600&display=swap'); 2 | 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; 6 | 7 | .font-['Inter'] { 8 | font-feature-settings: 'ss01' 1, 'ss02' 1, 'ss03' 1; 9 | } -------------------------------------------------------------------------------- /frontend/src/main.jsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react' 2 | import { createRoot } from 'react-dom/client' 3 | import './index.css' 4 | import App from './App.jsx' 5 | 6 | createRoot(document.getElementById('root')).render( 7 | 8 | 9 | , 10 | ) 11 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /backend/__init__.py: -------------------------------------------------------------------------------- 1 | # This file indicates that the directory is a Python package. 2 | 3 | # Import all necessary prompts 4 | from .prompts import ( 5 | prompt_00, 6 | prompt_01, 7 | prompt_02, 8 | prompt_04, 9 | prompt_05 10 | ) 11 | 12 | # You can expose specific functions/classes here if needed 13 | __all__ = [ 14 | 'prompt_00', 15 | 'prompt_01', 16 | 'prompt_02', 17 | 'prompt_04', 18 | 'prompt_05' 19 | ] -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # React + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | -------------------------------------------------------------------------------- /backend/install_repomix.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Check if Repomix is installed 4 | if ! command -v repomix &> /dev/null; then 5 | echo "Repomix is not installed. Installing via npm..." 6 | 7 | # Install Node.js (if not already available) 8 | apt-get update && apt-get install -y nodejs npm 9 | 10 | # Install Repomix globally 11 | npm install -g repomix || { 12 | echo "Error installing Repomix via npm" 13 | exit 1 14 | } 15 | 16 | echo "Repomix has been installed successfully via npm." 17 | else 18 | echo "Repomix is already installed." 19 | fi 20 | -------------------------------------------------------------------------------- /frontend/src/App.css: -------------------------------------------------------------------------------- 1 | .app { 2 | font-family: Arial, sans-serif; 3 | max-width: 600px; 4 | margin: 0 auto; 5 | padding: 20px; 6 | } 7 | 8 | button { 9 | background-color: #007BFF; 10 | color: white; 11 | padding: 10px 20px; 12 | border: none; 13 | cursor: pointer; 14 | margin: 10px 0; 15 | } 16 | 17 | button:disabled { 18 | background-color: #aaa; 19 | } 20 | 21 | input { 22 | width: 100%; 23 | padding: 10px; 24 | margin: 10px 0; 25 | border: 1px solid #ddd; 26 | } 27 | 28 | .preview { 29 | white-space: pre-wrap; 30 | word-wrap: break-word; 31 | background-color: #f9f9f9; 32 | padding: 20px; 33 | border-radius: 5px; 34 | margin-top: 20px; 35 | } 36 | 37 | pre { 38 | background-color: #f9f9f9; 39 | padding: 20px; 40 | border-radius: 5px; 41 | white-space: pre-wrap; 42 | word-wrap: break-word; 43 | } 44 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite-project", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@tailwindcss/typography": "^0.5.15", 14 | "axios": "^1.7.9", 15 | "react": "^18.3.1", 16 | "react-dom": "^18.3.1", 17 | "react-markdown": "^9.0.1" 18 | }, 19 | "devDependencies": { 20 | "@eslint/js": "^9.17.0", 21 | "@types/react": "^18.3.18", 22 | "@types/react-dom": "^18.3.5", 23 | "@vitejs/plugin-react": "^4.3.4", 24 | "autoprefixer": "^10.4.20", 25 | "eslint": "^9.17.0", 26 | "eslint-plugin-react": "^7.37.2", 27 | "eslint-plugin-react-hooks": "^5.0.0", 28 | "eslint-plugin-react-refresh": "^0.4.16", 29 | "globals": "^15.14.0", 30 | "postcss": "^8.4.49", 31 | "tailwindcss": "^3.4.17", 32 | "vite": "^6.0.5" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Jaimin Godhani 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /frontend/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import react from 'eslint-plugin-react' 4 | import reactHooks from 'eslint-plugin-react-hooks' 5 | import reactRefresh from 'eslint-plugin-react-refresh' 6 | 7 | export default [ 8 | { ignores: ['dist'] }, 9 | { 10 | files: ['**/*.{js,jsx}'], 11 | languageOptions: { 12 | ecmaVersion: 2020, 13 | globals: globals.browser, 14 | parserOptions: { 15 | ecmaVersion: 'latest', 16 | ecmaFeatures: { jsx: true }, 17 | sourceType: 'module', 18 | }, 19 | }, 20 | settings: { react: { version: '18.3' } }, 21 | plugins: { 22 | react, 23 | 'react-hooks': reactHooks, 24 | 'react-refresh': reactRefresh, 25 | }, 26 | rules: { 27 | ...js.configs.recommended.rules, 28 | ...react.configs.recommended.rules, 29 | ...react.configs['jsx-runtime'].rules, 30 | ...reactHooks.configs.recommended.rules, 31 | 'react/jsx-no-target-blank': 'off', 32 | 'react-refresh/only-export-components': [ 33 | 'warn', 34 | { allowConstantExport: true }, 35 | ], 36 | }, 37 | }, 38 | ] 39 | -------------------------------------------------------------------------------- /frontend/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | import typography from '@tailwindcss/typography'; 3 | 4 | export default { 5 | content: [ 6 | "./index.html", 7 | "./src/**/*.{js,ts,jsx,tsx}", 8 | ], 9 | theme: { 10 | extend: { 11 | typography: { 12 | DEFAULT: { 13 | css: { 14 | color: '#fff', 15 | a: { 16 | color: '#3b82f6', 17 | '&:hover': { 18 | color: '#60a5fa', 19 | }, 20 | }, 21 | h1: { 22 | color: '#fff', 23 | }, 24 | h2: { 25 | color: '#fff', 26 | }, 27 | h3: { 28 | color: '#fff', 29 | }, 30 | strong: { 31 | color: '#fff', 32 | }, 33 | code: { 34 | color: '#fff', 35 | background: '#1f2937', 36 | padding: '0.2em 0.4em', 37 | borderRadius: '0.25rem', 38 | }, 39 | pre: { 40 | background: '#1f2937', 41 | }, 42 | }, 43 | }, 44 | }, 45 | keyframes: { 46 | fadeIn: { 47 | '0%': { opacity: '0', transform: 'translateY(10px)' }, 48 | '100%': { opacity: '1', transform: 'translateY(0)' }, 49 | }, 50 | loadingDot: { 51 | '0%': { transform: 'translateY(0px)' }, 52 | '50%': { transform: 'translateY(-10px)' }, 53 | '100%': { transform: 'translateY(0px)' }, 54 | }, 55 | }, 56 | animation: { 57 | fadeIn: 'fadeIn 0.5s ease-out forwards', 58 | 'loading-dot-1': 'loadingDot 0.9s infinite', 59 | 'loading-dot-2': 'loadingDot 0.9s infinite 0.3s', 60 | 'loading-dot-3': 'loadingDot 0.9s infinite 0.6s', 61 | }, 62 | }, 63 | }, 64 | plugins: [typography], 65 | } 66 | 67 | -------------------------------------------------------------------------------- /frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | docSmith – AI-Powered Documentation & README Generator 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /frontend/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /backend/prompts.py: -------------------------------------------------------------------------------- 1 | prompt_01 = """ 2 | You are an experienced technical documentation expert. Your task is to analyze the provided codebase and create **comprehensive, well-structured, and user-friendly documentation**. The output must be in **Markdown format**. 3 | 4 | ### Codebase: 5 | {codebase} 6 | 7 | ### Documentation Guidelines: 8 | 9 | Organize the documentation into the following sections. If a section is not applicable, omit it. Add any additional sections or subsections as necessary to enhance clarity and usability. Use **Markdown styling** to maintain a clear hierarchy with headings and subheadings: 10 | 11 | **Project Overview** 12 | - Purpose of the project 13 | - Key features 14 | - Supported platforms or requirements 15 | 16 | **Getting Started** 17 | - Installation or setup instructions 18 | - Dependencies or prerequisites 19 | 20 | **Usage**(only if applicable) 21 | - How to use the project in brief 22 | - Code snippets or examples(very minimal and brief) 23 | - Don't generate this section if not required 24 | 25 | **Code Structure**(only if applicable) 26 | - Folder and file organization 27 | - Brief descriptions of key components 28 | 29 | **API Documentation** (if applicable) 30 | - Endpoints (GET, POST, etc.) 31 | - Input and output formats 32 | - Example API requests and responses 33 | 34 | **Contributing** (if applicable) 35 | - Contribution guidelines 36 | - Code style and best practices 37 | 38 | **FAQ** (if applicable) 39 | - Common issues and resolutions 40 | 41 | **License** (if applicable) 42 | - Licensing details 43 | 44 | ### Additional Notes: 45 | - Use appropriate Markdown headers (#, ##, ###, etc.) for section hierarchy. 46 | - Ensure clarity by using lists, tables, or code blocks (`code`) where helpful. 47 | - Whenever using code block The code sequence must end with '' sequence. 48 | - Keep the language simple and concise. 49 | - Provide examples or explanations where needed to ensure comprehensibility for users of varying expertise. 50 | 51 | --- 52 | """ 53 | 54 | prompt_04 = """ 55 | You are an experienced Docker expert and DevOps engineer. Your task is to generate a **Dockerfile** for the provided project. Please analyze the project details below and create a Dockerfile that adheres to Docker best practices. 56 | 57 | ### Project Details: 58 | {codebase} 59 | 60 | ### Dockerfile Requirements: 61 | - Choose an appropriate base image that aligns with the project's technology stack. 62 | - Install any necessary dependencies. 63 | - Copy the project source code into the container. 64 | - Expose any required ports. 65 | - Define the command to run the application. 66 | - Use multi-stage builds if applicable for optimization. 67 | - Ensure the Dockerfile is production-ready and optimized. 68 | - Use best practices for security and efficiency. 69 | 70 | Provide the final output as a valid Dockerfile without any additional commentary. 71 | """ 72 | 73 | prompt_05 = """ 74 | You are an experienced Docker Compose expert and DevOps engineer. The entire codebase is provided below as a repomix generated file. Your task is to analyze the codebase to identify all necessary services, dependencies, and configurations, and then generate a complete and optimized **docker-compose** configuration. 75 | 76 | ### Provided Codebase (repomix generated file): 77 | {codebase} 78 | 79 | ### docker-compose Generation Guidelines: 80 | - Analyze the codebase to determine all required services (e.g., web server, databases, caching systems, etc.). 81 | - For each service, specify the build context or Docker image as appropriate. 82 | - Configure essential settings including ports, environment variables, volumes, and networks. 83 | - Define any dependencies between services and ensure the correct startup order. 84 | - Ensure the configuration adheres to docker-compose best practices and is compatible with version '3' or higher. 85 | - Optimize the configuration for the intended environment (development or production). 86 | - Use best practices for security and efficiency. 87 | 88 | Provide the final output as a valid docker-compose configuration in YAML format without any additional commentary. 89 | """ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # docSmith 2 | docSmith is an API designed to generate documentation, Dockerfiles, and Docker Compose configurations from a GitHub repository URL. It leverages the Gemini language model and Langchain to analyze the codebase and produce structured outputs. 3 | [Screenshot 2025-02-15 at 11 44 21 PM](https://doc-smith.vercel.app/) 4 | 5 | **Key Features:** 6 | * **Documentation Generation:** Automatically generates comprehensive documentation for a given GitHub repository. 7 | * **Dockerfile Generation:** Creates a Dockerfile tailored to the project's needs. 8 | * **Docker Compose Generation:** Generates a Docker Compose configuration for multi-container applications. 9 | * **API-Driven:** Provides a RESTful API for easy integration with other tools and services. 10 | **Supported Platforms/Requirements:** 11 | * Python 3.7+ 12 | * FastAPI 13 | * Langchain 14 | * Google Gemini API 15 | * Docker (for Dockerfile and Docker Compose generation) 16 | * Node.js and npm (for Repomix installation) 17 | ## Getting Started 18 | ### Prerequisites 19 | Before you begin, ensure you have the following installed: 20 | * **Python:** Version 3.7 or higher. 21 | * **pip:** Python package installer. 22 | * **Docker:** For generating and using Dockerfiles and Docker Compose configurations. 23 | * **Node.js and npm:** Required for installing Repomix. 24 | * **Google Cloud Account:** Required to access the Gemini API. 25 | * **Git:** Required to clone the repository. 26 | ### Installation 27 | 1. **Clone the repository:** 28 | ```bash 29 | git clone 30 | cd 31 | ``` 32 | 2. **Create a virtual environment (recommended):** 33 | ```bash 34 | python3 -m venv venv 35 | source venv/bin/activate # On Linux/macOS 36 | venv\Scripts\activate # On Windows 37 | ``` 38 | 3. **Install the backend dependencies:** 39 | ```bash 40 | cd backend 41 | pip install -r requirements.txt 42 | ``` 43 | 4. **Set up environment variables:** 44 | * Create a `.env` file in the `backend` directory. 45 | * Add your Google API key: 46 | GOOGLE_API_KEY= 47 | 48 | 5. **Install Repomix:** 49 | ```bash 50 | npm install -g repomix 51 | ``` 52 | This script checks if Repomix is installed and installs it globally using npm if it's not already present. 53 | 6. **Install the frontend dependencies:** 54 | ```bash 55 | cd ../frontend 56 | npm install 57 | ``` 58 | ### Running the Application 59 | 1. **Start the backend server:** 60 | ```bash 61 | cd ../backend 62 | uvicorn main:app --host 0.0.0.0 --port 8000 --reload 63 | ``` 64 | This command starts the FastAPI server on port 8000 with hot reloading enabled. 65 | 2. **Start the frontend application:** 66 | ```bash 67 | cd ../frontend 68 | npm run dev 69 | ``` 70 | This command starts the Vite development server, typically on port 5173. 71 | 72 | ## API Documentation 73 | The backend provides the following API endpoints: 74 | * **`POST /generate-docs-from-url`:** Generates documentation from a GitHub repository URL. 75 | * **Input:** 76 | ```json 77 | { 78 | "url": "https://github.com/username/repository" 79 | } 80 | * **Output:** A string containing the generated documentation in Markdown format. 81 | * **`POST /generate-dockerfile`:** Generates a Dockerfile from a GitHub repository URL. 82 | * **Input:** 83 | ```json 84 | { 85 | "url": "https://github.com/username/repository" 86 | } 87 | * **Output:** A string containing the generated Dockerfile. 88 | * **`POST /generate-docker-compose`:** Generates a Docker Compose configuration from a GitHub repository URL. 89 | * **Input:** 90 | ```json 91 | { 92 | "url": "https://github.com/username/repository" 93 | } 94 | * **Output:** A string containing the generated Docker Compose configuration in YAML format. 95 | * **`GET /ping`:** A lightweight route to keep the server alive. 96 | * **Output:** 97 | ```json 98 | { 99 | "message": "Pong!" 100 | } 101 | 102 | -------------------------------------------------------------------------------- /backend/main.py: -------------------------------------------------------------------------------- 1 | from typing import Union, Dict, Any 2 | from pydantic import BaseModel, HttpUrl 3 | from fastapi import FastAPI, File, UploadFile, HTTPException, Body, Request 4 | from fastapi.responses import JSONResponse 5 | from tenacity import retry, wait_random_exponential, stop_after_attempt 6 | from langchain_core.language_models import BaseChatModel 7 | from langchain_google_genai import ChatGoogleGenerativeAI 8 | from langchain.prompts import PromptTemplate 9 | from langchain_core.runnables import RunnableSequence 10 | from fastapi.middleware.cors import CORSMiddleware 11 | from dotenv import load_dotenv 12 | from prompts import prompt_04, prompt_01, prompt_05 13 | import re 14 | import os 15 | import json 16 | import uvicorn 17 | import logging 18 | import subprocess 19 | 20 | @retry(wait=wait_random_exponential(multiplier=1, max=60), stop=stop_after_attempt(3)) 21 | async def retry_llm_call(chain, input_data): 22 | """ 23 | Wrapper function to retry LLM calls with exponential backoff 24 | """ 25 | try: 26 | print(f"Trying LLM call... Input size: {len(input_data['codebase'])} characters") 27 | return await chain.ainvoke(input_data) 28 | except Exception as e: 29 | print(f"LLM call failed: {str(e)}") 30 | # Check for specific error types 31 | if "event loop" in str(e).lower(): 32 | print("Event loop error detected. Ensure LLM is initialized properly within async context.") 33 | elif "429" in str(e): 34 | print("Rate limit error detected. Consider implementing a delay.") 35 | raise e 36 | 37 | # Initialize FastAPI application with metadata 38 | app = FastAPI( 39 | title="Documentation Generator API", 40 | description="API for generating structured documentation from codebase using Gemini and Langchain", 41 | version="1.0.0" 42 | ) 43 | # Allow CORS for all origins (can change this if needed) 44 | app.add_middleware( 45 | CORSMiddleware, 46 | allow_origins=["*"], 47 | allow_credentials=True, 48 | allow_methods=["*"], 49 | allow_headers=["*"], 50 | ) 51 | 52 | @app.middleware("http") 53 | async def handle_429_errors(request: Request, call_next): 54 | try: 55 | response = await call_next(request) 56 | return response 57 | except Exception as e: 58 | if "429" in str(e): 59 | return JSONResponse( 60 | status_code=429, 61 | content={ 62 | "detail": "Resource exhausted. Please try again later.", 63 | "retry_after": "60" 64 | } 65 | ) 66 | raise e 67 | 68 | # Load environment variables 69 | load_dotenv() 70 | 71 | # Create a function to get the LLM instance within async context 72 | def create_llm(): 73 | return ChatGoogleGenerativeAI( 74 | model="gemini-2.0-flash", 75 | google_api_key=os.getenv("GOOGLE_API_KEY"), 76 | temperature=0.1, 77 | max_output_tokens=4096 78 | ) 79 | 80 | 81 | # New API input model for GitHub URL 82 | class RepoURL(BaseModel): 83 | url: HttpUrl 84 | 85 | import tempfile 86 | 87 | async def run_repomix(repo_url: str) -> str: 88 | """ 89 | Run Repomix on a remote GitHub repository to generate a .txt file containing the codebase content. 90 | """ 91 | try: 92 | # Convert the URL to a string 93 | repo_url = str(repo_url) 94 | 95 | # Create a temporary directory for the output file 96 | temp_dir = tempfile.mkdtemp() 97 | 98 | # Define the path for the output .txt file 99 | packed_file_path = os.path.join(temp_dir, "packed_codebase.txt") 100 | 101 | # Run Repomix on the remote GitHub repository to create the .txt file 102 | result = subprocess.run( 103 | [ 104 | "repomix", 105 | "--remote", repo_url, 106 | "--output", packed_file_path 107 | ], 108 | check=True, 109 | stdout=subprocess.PIPE, 110 | stderr=subprocess.PIPE 111 | ) 112 | 113 | if result.returncode != 0: 114 | raise RuntimeError(f"Repomix failed: {result.stderr.decode()}") 115 | 116 | # print(f"Packed codebase .txt file created at {packed_file_path}") 117 | 118 | # Return the path to the packed .txt file 119 | return packed_file_path 120 | 121 | except subprocess.CalledProcessError as e: 122 | raise RuntimeError(f"Error during Repomix execution: {str(e)}") 123 | except Exception as e: 124 | raise RuntimeError(f"Error in processing remote repository: {str(e)}") 125 | 126 | 127 | @app.post("/generate-docs-from-url") 128 | async def generate_docs_from_url(repo_url: RepoURL): 129 | """ 130 | Generate documentation directly from a remote GitHub repository. 131 | """ 132 | try: 133 | normalized_url = str(repo_url.url).rstrip("/") 134 | if not is_valid_github_url(normalized_url): 135 | raise HTTPException(status_code=400, detail="Invalid GitHub repository URL.") 136 | 137 | print(normalized_url) 138 | 139 | # Run repomix with the remote URL to get the packed codebase as a .txt file 140 | packed_file = await run_repomix(normalized_url) 141 | 142 | with open(packed_file, "r", encoding="utf-8") as f: 143 | codebase_content = f.read() 144 | 145 | # Check the size and truncate if necessary 146 | content_size = len(codebase_content) 147 | print(f"Original codebase size: {content_size} characters") 148 | 149 | # Limit to approximately 900k characters (adjust as needed) 150 | MAX_SIZE = 2000000 151 | if content_size > MAX_SIZE: 152 | print(f"Truncating codebase from {content_size} to {MAX_SIZE} characters") 153 | codebase_content = codebase_content[:MAX_SIZE] 154 | # Add a note about truncation 155 | codebase_content += "\n\n[Note: This codebase was truncated due to size limitations.]" 156 | 157 | # Create LLM instance within async context 158 | llm = create_llm() 159 | 160 | dockerfile_prompt = PromptTemplate( 161 | input_variables=["codebase"], 162 | template=prompt_01 163 | ) 164 | dockerfile_chain = dockerfile_prompt | llm 165 | 166 | # Use retry wrapper here 167 | result = await retry_llm_call(dockerfile_chain, {"codebase": codebase_content}) 168 | return result.content 169 | 170 | except Exception as e: 171 | if "429" in str(e): 172 | raise HTTPException( 173 | status_code=429, 174 | detail="Resource exhausted. Please try again later." 175 | ) 176 | raise HTTPException( 177 | status_code=500, 178 | detail=f"Documentation generation failed: {str(e)}" 179 | ) 180 | 181 | @app.post("/generate-dockerfile") 182 | async def generate_dockerfile(repo_url: RepoURL): 183 | """ 184 | Generate Dockerfile from a remote GitHub repository. 185 | """ 186 | try: 187 | normalized_url = str(repo_url.url).rstrip("/") 188 | print(normalized_url) 189 | if not is_valid_github_url(normalized_url): 190 | raise HTTPException(status_code=400, detail="Invalid GitHub repository URL.") 191 | 192 | packed_file = await run_repomix(normalized_url) 193 | 194 | with open(packed_file, "r", encoding="utf-8") as f: 195 | codebase_content = f.read() 196 | 197 | # Use prompt_04 for Dockerfile generation 198 | dockerfile_prompt = PromptTemplate( 199 | input_variables=["codebase"], 200 | template=prompt_04 201 | ) 202 | dockerfile_chain = dockerfile_prompt | create_llm() 203 | 204 | result = await dockerfile_chain.ainvoke({"codebase": codebase_content}) 205 | return result.content 206 | 207 | except Exception as e: 208 | raise HTTPException( 209 | status_code=500, 210 | detail=f"Dockerfile generation failed: {str(e)}" 211 | ) 212 | 213 | @app.post("/generate-docker-compose") 214 | async def generate_docker_compose(repo_url: RepoURL): 215 | """ 216 | Generate Docker Compose configuration from a remote GitHub repository. 217 | """ 218 | try: 219 | normalized_url = str(repo_url.url).rstrip("/") 220 | print(normalized_url) 221 | if not is_valid_github_url(normalized_url): 222 | raise HTTPException(status_code=400, detail="Invalid GitHub repository URL.") 223 | 224 | packed_file = await run_repomix(normalized_url) 225 | 226 | with open(packed_file, "r", encoding="utf-8") as f: 227 | codebase_content = f.read() 228 | 229 | # Use prompt_05 for Docker Compose generation 230 | compose_prompt = PromptTemplate( 231 | input_variables=["codebase"], 232 | template=prompt_05 233 | ) 234 | compose_chain = compose_prompt | create_llm() 235 | 236 | result = await compose_chain.ainvoke({"codebase": codebase_content}) 237 | return result.content 238 | 239 | except Exception as e: 240 | raise HTTPException( 241 | status_code=500, 242 | detail=f"Docker Compose generation failed: {str(e)}" 243 | ) 244 | 245 | @app.get("/ping") 246 | async def ping(): 247 | """ 248 | A lightweight route to keep the server alive. 249 | """ 250 | return {"message": "Pong!"} 251 | 252 | def is_valid_github_url(url: str) -> bool: 253 | """Check if the URL is a valid GitHub repository link.""" 254 | # Updated pattern to accept URLs with optional .git suffix 255 | pattern = r"^https://github\.com/[\w-]+/[\w-]+(?:\.git)?/?$" 256 | return re.match(pattern, url) is not None 257 | 258 | 259 | 260 | if __name__ == "__main__": 261 | logging.basicConfig(level=logging.INFO) 262 | uvicorn.run( 263 | "main:app", 264 | host="0.0.0.0", 265 | port=8000, 266 | reload=True 267 | ) 268 | -------------------------------------------------------------------------------- /frontend/src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import axios from 'axios'; 3 | import ReactMarkdown from 'react-markdown'; 4 | 5 | function App() { 6 | const [repoUrl, setRepoUrl] = useState(''); 7 | const [documentation, setDocumentation] = useState(''); 8 | const [loading, setLoading] = useState(false); 9 | const [view, setView] = useState('preview'); 10 | const [selectedOption, setSelectedOption] = useState('documentation'); 11 | const [isDropdownOpen, setIsDropdownOpen] = useState(false); 12 | const [editedContent, setEditedContent] = useState(''); 13 | const [hasGenerated, setHasGenerated] = useState(false); 14 | 15 | const normalizeGitHubUrl = (url) => { 16 | try { 17 | // Remove trailing slash 18 | url = url.trim().replace(/\/$/, ''); 19 | 20 | // If URL doesn't start with http/https, add https:// 21 | if (!url.startsWith('http://') && !url.startsWith('https://')) { 22 | url = 'https://' + url; 23 | } 24 | 25 | // Remove www. if present 26 | url = url.replace('www.', ''); 27 | 28 | // Parse URL to validate and normalize 29 | const urlObj = new URL(url); 30 | 31 | // Ensure it's a GitHub URL 32 | if (!urlObj.hostname.endsWith('github.com')) { 33 | throw new Error('Not a GitHub URL'); 34 | } 35 | 36 | // Get the path parts 37 | const pathParts = urlObj.pathname.split('/').filter(Boolean); 38 | 39 | // Need at least username/repo 40 | if (pathParts.length < 2) { 41 | throw new Error('Invalid repository path'); 42 | } 43 | 44 | // Construct final URL with just username/repo 45 | return `https://github.com/${pathParts[0]}/${pathParts[1]}`; 46 | } catch (error) { 47 | console.error(`Error normalizing GitHub URL:`, error); 48 | return null; 49 | } 50 | }; 51 | 52 | const handleGenerateDocs = async () => { 53 | if (!repoUrl) return; 54 | 55 | const normalizedUrl = normalizeGitHubUrl(repoUrl); 56 | if (!normalizedUrl) { 57 | alert('Please enter a valid GitHub repository URL'); 58 | return; 59 | } 60 | 61 | setLoading(true); 62 | try { 63 | let endpoint = ''; 64 | switch (selectedOption) { 65 | case 'documentation': 66 | endpoint = 'generate-docs-from-url'; 67 | break; 68 | case 'dockerfile': 69 | endpoint = 'generate-dockerfile'; 70 | break; 71 | case 'docker-compose': 72 | endpoint = 'generate-docker-compose'; 73 | break; 74 | default: 75 | endpoint = 'generate-docs-from-url'; 76 | } 77 | 78 | const response = await axios.post(`https://docsmith.onrender.com/${endpoint}`, { 79 | url: normalizedUrl, 80 | type: selectedOption 81 | }); 82 | 83 | setDocumentation(response.data); 84 | setHasGenerated(true); 85 | } catch (error) { 86 | console.error(`Error generating ${selectedOption}:`, error); 87 | alert(`Failed to generate ${selectedOption}`); 88 | } 89 | setLoading(false); 90 | }; 91 | 92 | const handleCopy = () => { 93 | const contentToCopy = view === 'edit' ? editedContent : formatDocumentation(documentation); 94 | navigator.clipboard.writeText(contentToCopy).then(() => { 95 | alert('Content copied!'); 96 | }); 97 | }; 98 | 99 | const formatDocumentation = (doc) => { 100 | if (!doc) return ''; 101 | 102 | // Different formatting based on the selected option 103 | if (selectedOption === 'documentation') { 104 | return doc 105 | .replace(/```markdown\n/g, '') // Remove opening markdown marker 106 | .replace(/```yaml\n/g, '') // Remove opening yaml marker 107 | .replace(/```dockerfile\n/g, '') // Remove opening dockerfile marker 108 | .replace(/```\n/g, '') // Remove opening marker 109 | .replace(/```$/gm, '') // Remove closing marker at end of lines 110 | .replace(//g, '```') // Replace with triple backticks 111 | .replace(/\\n/g, '\n') 112 | .replace(/\n\n/g, '\n'); 113 | } else { 114 | // For Dockerfile and Docker Compose 115 | return doc 116 | .replace(/```dockerfile\n/g, '') // Remove opening dockerfile marker 117 | .replace(/```yaml\n/g, '') // Remove opening yaml marker 118 | .replace(/```\n/g, '') // Remove opening marker 119 | .replace(/```$/gm, '') // Remove closing marker at end of lines 120 | .replace(//g, '```') // Replace with triple backticks 121 | .replace(/\\n/g, '\n') 122 | .replace(/\n\n/g, '\n'); 123 | } 124 | }; 125 | 126 | const handleKeyPress = (e) => { 127 | if (e.key === 'Enter') { 128 | handleGenerateDocs(); 129 | } 130 | }; 131 | 132 | useEffect(() => { 133 | if (documentation) { 134 | setEditedContent(formatDocumentation(documentation)); 135 | } 136 | }, [documentation]); 137 | 138 | return ( 139 |
140 | {/* Background Pattern */} 141 |
142 | {/* Gradient Overlay */} 143 |
144 | 145 | {/* Grid Pattern */} 146 |
156 | 157 | {/* Floating Elements */} 158 |
159 | {[...Array(3)].map((_, i) => ( 160 |
170 | ))} 171 |
172 |
173 | 174 | {/* Content Wrapper - Add relative and z-10 to ensure content stays above background */} 175 |
176 | {/* Header */} 177 |
178 | 217 |
218 | 219 | {/* Main Content */} 220 |
223 |
226 |
227 |

230 | 231 | Generate documentation from a 232 | 233 |
234 | 235 | GitHub repository URL. 236 | 237 |

238 | 239 | {/* Search Input */} 240 |
241 |
242 |
243 | setRepoUrl(e.target.value)} 247 | onKeyPress={handleKeyPress} 248 | placeholder="Enter a GitHub repository URL..." 249 | className="w-full bg-[#161B22] border border-gray-700/50 rounded-lg px-4 py-3 pr-[8.5rem] sm:pr-36 focus:outline-none focus:ring-2 focus:ring-blue-500/50 text-white placeholder-gray-500 transition-all duration-200 text-sm sm:text-base" 250 | /> 251 |
252 | 267 | 278 |
279 |
280 | 281 | {/* Dropdown Menu */} 282 | {isDropdownOpen && ( 283 |
284 | {['documentation', 'dockerfile', 'docker-compose'].map((option) => ( 285 | 301 | ))} 302 |
303 | )} 304 |
305 | 306 | {/* Loading State */} 307 | {loading && ( 308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 | 319 | 324 | 332 | 337 | 338 | Generating {selectedOption}... 339 | 340 |
341 |
342 | This might take a few seconds 343 |
344 |
345 | )} 346 | 347 | {/* Results */} 348 | {documentation && !loading && ( 349 |
350 | {/* Results Header */} 351 |
352 |
353 | 364 | 375 |
376 | 390 |
391 | 392 | {/* Results Content */} 393 |
394 | {view === 'edit' ? ( 395 |
396 |
397 |
398 |
399 | {selectedOption === 'dockerfile' ? 'Dockerfile' : selectedOption === 'docker-compose' ? 'docker-compose.yml' : 'README.md'} 400 |
401 | 402 | {selectedOption === 'dockerfile' ? 'Docker' : selectedOption === 'docker-compose' ? 'YAML' : 'Markdown'} 403 | 404 |
405 |
406 |