├── 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 | [
](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 |
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 |
317 |
318 |
319 |
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 |
413 |
414 | ) : (
415 |
420 | {selectedOption === 'documentation' ? (
421 |
422 | {editedContent}
423 |
424 | ) : (
425 |
426 |
427 |
428 |
429 |
{selectedOption === 'dockerfile' ? 'Dockerfile' : 'docker-compose.yml'}
430 |
431 |
432 | {selectedOption === 'dockerfile' ? 'Docker' : 'YAML'}
433 |
434 |
435 |
436 |
437 |
438 | {editedContent}
439 |
440 |
441 |
442 |
443 | )}
444 |
445 | )}
446 |
447 |
448 | )}
449 |
450 |
451 |
452 |
453 |
454 | );
455 | }
456 |
457 | export default App;
458 |
--------------------------------------------------------------------------------