Ankit Singh commited on
Commit
cef8a01
·
1 Parent(s): c385578

added Readme geneartor code

Browse files
Files changed (5) hide show
  1. app.py +13 -0
  2. requirements.txt +0 -0
  3. src/file_scanner.py +84 -0
  4. src/generator.py +191 -0
  5. src/ui.py +108 -0
app.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from src.ui import create_ui
3
+
4
+ def main():
5
+ # Launch the UI
6
+ try:
7
+ demo = create_ui()
8
+ demo.launch(show_error=True)
9
+ except Exception as e:
10
+ print(f"Error launching Gradio app: {e}")
11
+
12
+ if __name__ == "__main__":
13
+ main()
requirements.txt ADDED
Binary file (88 Bytes). View file
 
src/file_scanner.py ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import re
4
+ from typing import Dict, Any
5
+
6
+ def analyze_code_for_purpose(file_path: str) -> str:
7
+ """
8
+ Analyze a Python file to extract purpose from docstrings or comments.
9
+ """
10
+ purpose = []
11
+ try:
12
+ with open(file_path, 'r', encoding='utf-8') as file:
13
+ lines = file.readlines()
14
+ # Extract comments or docstrings from the first 30 lines
15
+ for line in lines[:30]:
16
+ # Match docstrings or comments
17
+ if re.match(r'^\s*#', line) or re.match(r'^\s*"""', line):
18
+ purpose.append(line.strip("# ").strip('"""').strip())
19
+ except Exception:
20
+ pass
21
+ return " ".join(purpose)[:250] # Limit to 250 characters
22
+
23
+ def quick_scan_project(project_path: str) -> Dict[str, Any]:
24
+ """
25
+ Perform a detailed scan of the project directory, including purpose analysis.
26
+ """
27
+ def get_all_files_and_dirs(path):
28
+ files = []
29
+ dirs = []
30
+ for root, directories, filenames in os.walk(path):
31
+ for file in filenames:
32
+ files.append(os.path.relpath(os.path.join(root, file), path))
33
+ for dir in directories:
34
+ dirs.append(os.path.relpath(os.path.join(root, dir), path))
35
+ return {
36
+ 'files': files[:10], # Limit to 10 for concise output
37
+ 'directories': dirs[:10]
38
+ }
39
+
40
+ scan_result = get_all_files_and_dirs(project_path)
41
+ top_level_files = os.listdir(project_path)
42
+
43
+ # Detect language and main file
44
+ main_files = {
45
+ 'Python': ['main.py', 'app.py', 'index.py'],
46
+ 'JavaScript': ['index.js', 'app.js', 'server.js'],
47
+ 'Java': ['Main.java', 'App.java']
48
+ }
49
+
50
+ language = 'Unknown'
51
+ main_file = ''
52
+ project_purpose = ''
53
+ for lang, possible_mains in main_files.items():
54
+ for main in possible_mains:
55
+ if main in top_level_files:
56
+ language = lang
57
+ main_file = main
58
+ project_purpose = analyze_code_for_purpose(os.path.join(project_path, main))
59
+ break
60
+ if language != 'Unknown':
61
+ break
62
+
63
+ # Detect dependencies
64
+ dependencies = []
65
+ try:
66
+ if 'requirements.txt' in top_level_files:
67
+ with open(os.path.join(project_path, 'requirements.txt'), 'r') as f:
68
+ dependencies = [line.strip().split('==')[0] for line in f
69
+ if line.strip() and not line.startswith('#')][:5]
70
+ elif 'package.json' in top_level_files:
71
+ with open(os.path.join(project_path, 'package.json'), 'r') as f:
72
+ package_data = json.load(f)
73
+ dependencies = list(package_data.get('dependencies', {}).keys())[:5]
74
+ except Exception:
75
+ pass
76
+
77
+ return {
78
+ "files": scan_result['files'],
79
+ "directories": scan_result['directories'],
80
+ "main_file": main_file or "Main file not detected",
81
+ "language": language,
82
+ "dependencies": dependencies or ["No dependencies detected"],
83
+ "purpose": project_purpose or "No purpose detected from the code"
84
+ }
src/generator.py ADDED
@@ -0,0 +1,191 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import os
3
+ import re
4
+ from typing import Any, Dict, List
5
+ from langchain_groq import ChatGroq
6
+ from langchain_core.prompts import ChatPromptTemplate
7
+ from langchain_core.output_parsers import StrOutputParser
8
+ from .file_scanner import quick_scan_project
9
+
10
+ class AIReadmeGenerator:
11
+ def __init__(self, api_key: str):
12
+ self.llm = ChatGroq(
13
+ api_key=api_key,
14
+ model_name="llama3-70b-8192"
15
+ )
16
+
17
+ def read_file_content(self, file_path: str) -> str:
18
+ """
19
+ Read the content of a file, handling different encodings and file sizes.
20
+
21
+ Args:
22
+ file_path (str): Path to the file to read
23
+
24
+ Returns:
25
+ str: File content or error message
26
+ """
27
+ try:
28
+ # Limit file reading to prevent memory issues
29
+ with open(file_path, 'r', encoding='utf-8') as file:
30
+ # Read first 10000 characters to prevent large files from overwhelming the system
31
+ return file.read(10000)
32
+ except Exception as e:
33
+ return f"Error reading file {file_path}: {str(e)}"
34
+
35
+ def analyze_project_structure(self, project_path: str) -> Dict[str, Any]:
36
+ """
37
+ Perform a comprehensive analysis of the project.
38
+
39
+ Args:
40
+ project_path (str): Path to the project directory
41
+
42
+ Returns:
43
+ Dict: Detailed project analysis
44
+ """
45
+ # Use existing quick_scan_project for initial analysis
46
+ project_info = quick_scan_project(project_path)
47
+
48
+ # Collect file contents
49
+ file_contents = {}
50
+ for file in project_info.get('files', []):
51
+ full_path = os.path.join(project_path, file)
52
+ if os.path.isfile(full_path):
53
+ file_contents[file] = self.read_file_content(full_path)
54
+
55
+ # Extract docstrings and comments from main files
56
+ code_insights = {}
57
+ main_files = ['app.py', 'main.py', 'src/ui.py', 'src/generator.py']
58
+ for main_file in main_files:
59
+ full_path = os.path.join(project_path, main_file)
60
+ if os.path.exists(full_path):
61
+ code_insights[main_file] = self.extract_code_insights(full_path)
62
+
63
+ # Combine all information
64
+ comprehensive_analysis = {
65
+ "project_structure": project_info,
66
+ "file_contents": file_contents,
67
+ "code_insights": code_insights,
68
+ "requirements": self.read_requirements(project_path)
69
+ }
70
+
71
+ return comprehensive_analysis
72
+
73
+ def extract_code_insights(self, file_path: str) -> Dict[str, str]:
74
+ """
75
+ Extract insights from Python files.
76
+
77
+ Args:
78
+ file_path (str): Path to the Python file
79
+
80
+ Returns:
81
+ Dict: Extracted insights including docstrings, key functions, etc.
82
+ """
83
+ insights = {
84
+ "module_docstring": "",
85
+ "key_functions": [],
86
+ "key_classes": []
87
+ }
88
+
89
+ try:
90
+ with open(file_path, 'r', encoding='utf-8') as file:
91
+ content = file.read()
92
+
93
+ # Extract module-level docstring
94
+ module_docstring_match = re.search(r'"""(.*?)"""', content, re.DOTALL)
95
+ if module_docstring_match:
96
+ insights['module_docstring'] = module_docstring_match.group(1).strip()
97
+
98
+ # Find key functions
99
+ function_matches = re.findall(r'def\s+(\w+)\(.*?\):\s*"""(.*?)"""', content, re.DOTALL)
100
+ insights['key_functions'] = [
101
+ f"{name}: {desc.strip()}"
102
+ for name, desc in function_matches
103
+ ]
104
+
105
+ # Find key classes
106
+ class_matches = re.findall(r'class\s+(\w+).*?:\s*"""(.*?)"""', content, re.DOTALL)
107
+ insights['key_classes'] = [
108
+ f"{name}: {desc.strip()}"
109
+ for name, desc in class_matches
110
+ ]
111
+ except Exception as e:
112
+ insights['error'] = str(e)
113
+
114
+ return insights
115
+
116
+ def read_requirements(self, project_path: str) -> List[str]:
117
+ """
118
+ Read project requirements file.
119
+
120
+ Args:
121
+ project_path (str): Path to the project directory
122
+
123
+ Returns:
124
+ List[str]: List of requirements
125
+ """
126
+ try:
127
+ req_path = os.path.join(project_path, 'requirements.txt')
128
+ if os.path.exists(req_path):
129
+ with open(req_path, 'r') as f:
130
+ return [line.strip() for line in f if line.strip() and not line.startswith('#')]
131
+ return []
132
+ except Exception:
133
+ return []
134
+
135
+ def generate_concise_readme(self, project_path: str) -> str:
136
+ """
137
+ Generate a README based on comprehensive project analysis.
138
+
139
+ Args:
140
+ project_path (str): Path to the project directory
141
+
142
+ Returns:
143
+ str: Generated README content
144
+ """
145
+ # Analyze the project comprehensively
146
+ project_analysis = self.analyze_project_structure(project_path)
147
+
148
+ # Prepare a detailed prompt for the LLM
149
+ template = """
150
+ Generate a comprehensive README.md based on the following project analysis:
151
+
152
+ PROJECT STRUCTURE:
153
+ {project_structure}
154
+
155
+ FILE CONTENTS:
156
+ {file_contents}
157
+
158
+ CODE INSIGHTS:
159
+ {code_insights}
160
+
161
+ REQUIREMENTS:
162
+ {requirements}
163
+
164
+ Based on this information, create a professional, detailed README.md that:
165
+ - Explains the project's purpose and functionality
166
+ - Describes key features and components
167
+ - Provides clear setup and usage instructions
168
+ - Highlights technical details
169
+ - Includes any relevant dependencies or prerequisites
170
+
171
+ Ensure the README is informative, well-structured, and tailored to the specific project.
172
+ """
173
+
174
+ # Prepare context for the LLM
175
+ context = {
176
+ "project_structure": str(project_analysis.get('project_structure', 'No structure information')),
177
+ "file_contents": '\n'.join([f"{k}:\n{v}" for k, v in project_analysis.get('file_contents', {}).items()]),
178
+ "code_insights": '\n'.join([f"{k}:\n{str(v)}" for k, v in project_analysis.get('code_insights', {}).items()]),
179
+ "requirements": '\n'.join(project_analysis.get('requirements', []))
180
+ }
181
+
182
+ try:
183
+ # Generate README using LLM
184
+ prompt = ChatPromptTemplate.from_template(template)
185
+ chain = prompt | self.llm | StrOutputParser()
186
+
187
+ readme_content = chain.invoke(context)
188
+ return readme_content.strip()
189
+ except Exception as e:
190
+ logging.error(f"README generation error: {str(e)}")
191
+ return f"# Project README\n\nUnable to generate README automatically.\n\nError: {str(e)}"
src/ui.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import gradio as gr
3
+ import tempfile
4
+ import zipfile
5
+ from groq import Groq
6
+ from dotenv import load_dotenv
7
+ from .generator import AIReadmeGenerator
8
+
9
+ # Validate the API key using the provided format
10
+ def validate_api_key(api_key):
11
+ """
12
+ Validate the API key using a test completion request.
13
+ """
14
+ try:
15
+ client = Groq(api_key=api_key)
16
+ response = client.chat.completions.create(
17
+ model="llama-3.1-8b-instant",
18
+ messages=[
19
+ {"role": "system", "content": "Validation test for API key."},
20
+ {"role": "user", "content": "Hello, world!"}
21
+ ],
22
+ temperature=0.1,
23
+ max_tokens=5,
24
+ )
25
+ if response and response.choices:
26
+ return True, "API key is valid!"
27
+ else:
28
+ return False, "API key validation failed: Empty response."
29
+ except Exception as e:
30
+ return False, f"Invalid API key. Error: {str(e)}"
31
+
32
+ # Generate README
33
+ def generate_readme_with_key(project_folder, api_key):
34
+ """
35
+ Generate the README for a given project folder using the provided API key.
36
+ """
37
+ try:
38
+ # Use the API key dynamically
39
+ generator = AIReadmeGenerator(api_key)
40
+ with tempfile.TemporaryDirectory() as temp_dir:
41
+ # Extract uploaded ZIP file
42
+ with zipfile.ZipFile(project_folder, 'r') as zip_ref:
43
+ zip_ref.extractall(temp_dir)
44
+
45
+ # Generate the README
46
+ readme_content = generator.generate_concise_readme(temp_dir)
47
+
48
+ return readme_content, readme_content
49
+ except zipfile.BadZipFile:
50
+ return "Error: Uploaded file is not a valid ZIP archive.", "Error: Invalid ZIP archive."
51
+ except Exception as e:
52
+ return f"Error generating README: {str(e)}", f"Error generating README: {str(e)}"
53
+
54
+ # Handle user login
55
+ def on_login(api_key):
56
+ is_valid, message = validate_api_key(api_key)
57
+ if is_valid:
58
+ return gr.update(visible=False), gr.update(visible=True), gr.Markdown(f"**Success!** {message}", visible=True)
59
+ else:
60
+ return gr.update(visible=True), gr.update(visible=False), gr.Markdown(f"**Error!** {message}", visible=True)
61
+
62
+ # Create Gradio UI
63
+ def create_ui():
64
+ with gr.Blocks(title="AI README Generator") as demo:
65
+ # Login Section
66
+ with gr.Column(visible=True) as login_section:
67
+ gr.Markdown("## Login to Access the AI README Generator")
68
+ api_key_input = gr.Textbox(label="Enter your API Key:", type="password")
69
+ login_button = gr.Button("Login")
70
+ login_feedback = gr.Markdown(visible=False)
71
+
72
+ gr.Markdown("""
73
+ ### Login Instructions
74
+ 1. **Sign Up / Log In** at [Groq API Portal](https://console.groq.com/keys).
75
+ 2. **Generate an API Key** in the API Keys section.
76
+ 3. **Copy and Paste the API Key** into the box above.
77
+ 4. **Click Login** to proceed.
78
+ """)
79
+
80
+ # README Generator Section
81
+ with gr.Column(visible=False) as readme_section:
82
+ gr.Markdown("# AI README Craft")
83
+ gr.Markdown("""
84
+ ## Instructions
85
+ - **Step 1:** Convert your project folder into a ZIP file.
86
+ - **Step 2:** Upload the ZIP file using the form below.
87
+ - **Step 3:** Click the **Generate README** button.
88
+ - **Note:** Larger files may take more time to process. Please be patient!
89
+ """)
90
+ folder_input = gr.File(file_count="single", type="filepath", label="📂 Upload Your Project (ZIP)")
91
+ generate_btn = gr.Button("✨ Generate README")
92
+ with gr.Row():
93
+ readme_output = gr.Textbox(lines=20, interactive=False, label="Generated README")
94
+ readme_markdown = gr.Markdown(label="README Preview")
95
+
96
+ # Button Click Events
97
+ login_button.click(
98
+ on_login,
99
+ inputs=[api_key_input],
100
+ outputs=[login_section, readme_section, login_feedback]
101
+ )
102
+ generate_btn.click(
103
+ generate_readme_with_key,
104
+ inputs=[folder_input, api_key_input],
105
+ outputs=[readme_output, readme_markdown]
106
+ )
107
+
108
+ return demo