Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,206 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import os
|
2 |
+
import zipfile
|
3 |
+
import shutil
|
4 |
+
import json
|
5 |
+
from flask import Flask, render_template, request, send_from_directory, session, redirect, url_for
|
6 |
+
import google.generativeai as genai
|
7 |
+
from werkzeug.utils import secure_filename
|
8 |
+
from flask_session import Session
|
9 |
+
from dotenv import load_dotenv
|
10 |
+
|
11 |
+
# Load .env variables
|
12 |
+
load_dotenv()
|
13 |
+
GEMINI_API_KEY = os.getenv("GEMINI_API_KEY")
|
14 |
+
|
15 |
+
# Config Gemini
|
16 |
+
genai.configure(api_key=GEMINI_API_KEY)
|
17 |
+
model = genai.GenerativeModel('gemini-1.5-pro-latest')
|
18 |
+
|
19 |
+
# Folders
|
20 |
+
UPLOAD_FOLDER = 'uploads'
|
21 |
+
EXTRACT_FOLDER = 'uploads_extracted'
|
22 |
+
OUTPUT_FOLDER = 'outputs'
|
23 |
+
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'docx', 'py', 'js', 'html', 'css', 'json', 'java', 'cpp', 'c', 'md', 'zip'}
|
24 |
+
|
25 |
+
# Flask app
|
26 |
+
app = Flask(__name__)
|
27 |
+
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
28 |
+
app.config['EXTRACT_FOLDER'] = EXTRACT_FOLDER
|
29 |
+
app.config['OUTPUT_FOLDER'] = OUTPUT_FOLDER
|
30 |
+
app.secret_key = 'supersecretkey'
|
31 |
+
app.config['SESSION_TYPE'] = 'filesystem'
|
32 |
+
Session(app)
|
33 |
+
|
34 |
+
# Ensure folders exist
|
35 |
+
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
36 |
+
os.makedirs(EXTRACT_FOLDER, exist_ok=True)
|
37 |
+
os.makedirs(OUTPUT_FOLDER, exist_ok=True)
|
38 |
+
|
39 |
+
# Helpers
|
40 |
+
def allowed_file(filename):
|
41 |
+
return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
|
42 |
+
|
43 |
+
def read_file(filepath):
|
44 |
+
try:
|
45 |
+
with open(filepath, 'r', encoding='utf-8', errors='ignore') as f:
|
46 |
+
return f.read()
|
47 |
+
except Exception as e:
|
48 |
+
return f"ERROR READING FILE: {str(e)}"
|
49 |
+
|
50 |
+
def save_txt(content, filename):
|
51 |
+
filepath = os.path.join(EXTRACT_FOLDER, filename)
|
52 |
+
os.makedirs(os.path.dirname(filepath), exist_ok=True)
|
53 |
+
with open(filepath, "w", encoding="utf-8") as f:
|
54 |
+
f.write(content)
|
55 |
+
|
56 |
+
def extract_zip(zip_path, extract_to):
|
57 |
+
with zipfile.ZipFile(zip_path, 'r') as zip_ref:
|
58 |
+
zip_ref.extractall(extract_to)
|
59 |
+
|
60 |
+
def get_project_tree():
|
61 |
+
tree = []
|
62 |
+
for root, dirs, files in os.walk(EXTRACT_FOLDER):
|
63 |
+
for name in files:
|
64 |
+
rel_path = os.path.relpath(os.path.join(root, name), EXTRACT_FOLDER)
|
65 |
+
tree.append(rel_path)
|
66 |
+
return tree
|
67 |
+
|
68 |
+
# Routes
|
69 |
+
@app.route('/', methods=['GET', 'POST'])
|
70 |
+
def index():
|
71 |
+
if request.method == 'POST':
|
72 |
+
# Clear previous project
|
73 |
+
shutil.rmtree(EXTRACT_FOLDER)
|
74 |
+
os.makedirs(EXTRACT_FOLDER)
|
75 |
+
|
76 |
+
files = request.files.getlist('files')
|
77 |
+
for file in files:
|
78 |
+
if file and allowed_file(file.filename):
|
79 |
+
filename = secure_filename(file.filename)
|
80 |
+
filepath = os.path.join(UPLOAD_FOLDER, filename)
|
81 |
+
file.save(filepath)
|
82 |
+
|
83 |
+
if filename.endswith('.zip'):
|
84 |
+
extract_zip(filepath, EXTRACT_FOLDER)
|
85 |
+
else:
|
86 |
+
save_txt(read_file(filepath), filename)
|
87 |
+
|
88 |
+
session['chat_history'] = []
|
89 |
+
session['ai_understood'] = True
|
90 |
+
session['project_tree'] = get_project_tree()
|
91 |
+
|
92 |
+
return redirect(url_for('chat'))
|
93 |
+
|
94 |
+
return render_template('index.html')
|
95 |
+
|
96 |
+
@app.route('/chat', methods=['GET', 'POST'])
|
97 |
+
def chat():
|
98 |
+
if 'ai_understood' not in session or not session['ai_understood']:
|
99 |
+
return redirect(url_for('index'))
|
100 |
+
|
101 |
+
ai_reply = ''
|
102 |
+
if request.method == 'POST':
|
103 |
+
user_message = request.form.get('user_message')
|
104 |
+
chat_history = session.get('chat_history', [])
|
105 |
+
|
106 |
+
# Build project context
|
107 |
+
all_text = ''
|
108 |
+
for file_path in get_project_tree():
|
109 |
+
full_path = os.path.join(EXTRACT_FOLDER, file_path)
|
110 |
+
content = read_file(full_path)
|
111 |
+
all_text += f"\n--- File: {file_path} ---\n{content}\n"
|
112 |
+
|
113 |
+
# Build chat prompt
|
114 |
+
chat_prompt = ''
|
115 |
+
for chat in chat_history:
|
116 |
+
chat_prompt += f"User: {chat['user']}\nAI: {chat['ai']}\n"
|
117 |
+
|
118 |
+
full_prompt = f"""
|
119 |
+
You are an expert AI project code editor.
|
120 |
+
The user uploaded this project:
|
121 |
+
|
122 |
+
{all_text}
|
123 |
+
|
124 |
+
Chat history:
|
125 |
+
{chat_prompt}
|
126 |
+
|
127 |
+
Now user says:
|
128 |
+
{user_message}
|
129 |
+
|
130 |
+
Please reply with:
|
131 |
+
- what files you edited (if any)
|
132 |
+
- content of new/edited files
|
133 |
+
- if user says DELETE file, remove it
|
134 |
+
- reply in readable format.
|
135 |
+
|
136 |
+
Example format:
|
137 |
+
|
138 |
+
### Edited Files:
|
139 |
+
File: filename.py
|
140 |
+
Content:
|
141 |
+
<content here>
|
142 |
+
|
143 |
+
### New Files:
|
144 |
+
File: newfile.py
|
145 |
+
Content:
|
146 |
+
<content here>
|
147 |
+
|
148 |
+
### Deleted Files:
|
149 |
+
filename1
|
150 |
+
filename2
|
151 |
+
|
152 |
+
### Notes:
|
153 |
+
<summary notes here>
|
154 |
+
"""
|
155 |
+
|
156 |
+
# Run Gemini API
|
157 |
+
response = model.generate_content(full_prompt)
|
158 |
+
ai_reply = response.text
|
159 |
+
|
160 |
+
# Parse AI reply to save files
|
161 |
+
if "### New Files:" in ai_reply:
|
162 |
+
parts = ai_reply.split("### New Files:")[-1].split("###")[0].strip().split("File:")
|
163 |
+
for part in parts[1:]:
|
164 |
+
lines = part.strip().split("\n")
|
165 |
+
filename = lines[0].strip()
|
166 |
+
content = "\n".join(lines[2:]).strip()
|
167 |
+
save_txt(content, filename)
|
168 |
+
|
169 |
+
if "### Edited Files:" in ai_reply:
|
170 |
+
parts = ai_reply.split("### Edited Files:")[-1].split("###")[0].strip().split("File:")
|
171 |
+
for part in parts[1:]:
|
172 |
+
lines = part.strip().split("\n")
|
173 |
+
filename = lines[0].strip()
|
174 |
+
content = "\n".join(lines[2:]).strip()
|
175 |
+
save_txt(content, filename)
|
176 |
+
|
177 |
+
if "### Deleted Files:" in ai_reply:
|
178 |
+
parts = ai_reply.split("### Deleted Files:")[-1].split("###")[0].strip().split("\n")
|
179 |
+
for filename in parts:
|
180 |
+
filename = filename.strip()
|
181 |
+
if filename:
|
182 |
+
path_to_delete = os.path.join(EXTRACT_FOLDER, filename)
|
183 |
+
if os.path.exists(path_to_delete):
|
184 |
+
os.remove(path_to_delete)
|
185 |
+
|
186 |
+
# Save chat history
|
187 |
+
chat_history.append({'user': user_message, 'ai': ai_reply})
|
188 |
+
session['chat_history'] = chat_history
|
189 |
+
session['project_tree'] = get_project_tree()
|
190 |
+
|
191 |
+
return render_template('chat.html',
|
192 |
+
chat_history=session.get('chat_history', []),
|
193 |
+
project_tree=session.get('project_tree', []),
|
194 |
+
ai_reply=ai_reply)
|
195 |
+
|
196 |
+
@app.route('/download_project')
|
197 |
+
def download_project():
|
198 |
+
# Zip project
|
199 |
+
zip_filename = 'project.zip'
|
200 |
+
zip_path = os.path.join(OUTPUT_FOLDER, zip_filename)
|
201 |
+
shutil.make_archive(os.path.splitext(zip_path)[0], 'zip', EXTRACT_FOLDER)
|
202 |
+
return send_from_directory(OUTPUT_FOLDER, zip_filename, as_attachment=True)
|
203 |
+
|
204 |
+
# Run app
|
205 |
+
if __name__ == '__main__':
|
206 |
+
app.run(debug=True)
|