Athspi commited on
Commit
dc5d038
·
verified ·
1 Parent(s): 39138c7

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +171 -98
app.py CHANGED
@@ -9,45 +9,47 @@ import google.generativeai as genai
9
  # Load environment variables
10
  load_dotenv()
11
 
12
- # Flask app setup
 
 
 
 
 
 
 
 
13
  app = Flask(__name__)
14
- app.config['SECRET_KEY'] = os.getenv("FLASK_SECRET_KEY", "insecure-key")
15
  app.config['SESSION_TYPE'] = 'filesystem'
16
  app.config['SESSION_PERMANENT'] = False
17
  app.config['SESSION_FILE_DIR'] = './flask_session'
18
  Session(app)
19
 
20
- # Folder setup
21
  UPLOAD_FOLDER = 'uploads'
22
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
23
  os.makedirs(UPLOAD_FOLDER, exist_ok=True)
24
  os.makedirs(app.config['SESSION_FILE_DIR'], exist_ok=True)
25
 
26
- # Gemini AI setup
27
- model = None
28
- gemini_key = os.getenv("GEMINI_API_KEY")
29
- if gemini_key:
30
- try:
31
- genai.configure(api_key=gemini_key)
32
- model = genai.GenerativeModel('gemini-2.0-pro')
33
- except Exception as e:
34
- print(f"Gemini setup failed: {e}")
35
-
36
- def get_project_files(project_path):
37
- files = {}
38
  for root, _, filenames in os.walk(project_path):
39
- for file in filenames:
40
- if file.startswith('.'): continue
41
- rel_path = os.path.relpath(os.path.join(root, file), project_path)
42
- try:
43
- with open(os.path.join(root, file), 'r', encoding='utf-8', errors='ignore') as f:
44
- files[rel_path] = f.read()
45
- except Exception as e:
46
- files[rel_path] = f"Error reading: {e}"
47
- return files
 
48
 
49
  @app.route('/')
50
  def index():
 
 
51
  if 'project_id' not in session:
52
  session['project_id'] = str(uuid.uuid4())
53
  session['chat_history'] = []
@@ -55,117 +57,188 @@ def index():
55
 
56
  @app.route('/upload', methods=['POST'])
57
  def upload():
58
- if 'project_id' not in session:
59
- session['project_id'] = str(uuid.uuid4())
60
- session['chat_history'] = []
61
 
62
  if 'project_zip' not in request.files:
63
- return jsonify({'error': 'No file uploaded'}), 400
 
64
  file = request.files['project_zip']
65
- if not file.filename.endswith('.zip'):
66
- return jsonify({'error': 'Only .zip supported'}), 400
67
 
68
- # Extract project
69
- project_id = session['project_id']
 
70
  project_path = os.path.join(app.config['UPLOAD_FOLDER'], project_id)
 
 
71
  if os.path.exists(project_path):
72
- for root, dirs, files in os.walk(project_path, topdown=False):
73
- for f in files: os.remove(os.path.join(root, f))
74
- for d in dirs: os.rmdir(os.path.join(root, d))
75
- os.makedirs(project_path, exist_ok=True)
76
 
 
77
  zip_path = os.path.join(project_path, file.filename)
78
  file.save(zip_path)
79
- with zipfile.ZipFile(zip_path, 'r') as z:
80
- z.extractall(project_path)
81
- os.remove(zip_path)
82
-
83
- # Read and prepare context
84
- files = get_project_files(project_path)
85
- prompt = "**Analyze this project and give suggestions for improvements, refactoring, or bugs.**\n\n"
86
- for path, content in files.items():
87
- prompt += f"File: `{path}`\n```\n{content[:1500]}\n```\n\n"
88
 
89
- # Ask Gemini
90
  try:
91
- ai_response = "Gemini model is not configured."
92
- if model:
93
- reply = model.generate_content(prompt)
94
- ai_response = reply.text
95
- except Exception as e:
96
- ai_response = f"Error talking to Gemini: {e}"
97
-
98
- # Save chat history
99
- session['chat_history'] = [
100
- {"role": "user", "parts": [prompt]},
101
- {"role": "model", "parts": [ai_response]}
102
- ]
103
- session.modified = True
104
-
105
- return jsonify({
106
- "message": "Project uploaded and analyzed by Gemini.",
107
- "file_tree": list(files.keys()),
108
- "chat_history": [
109
- {"role": "user", "content": "Project uploaded."},
 
 
 
 
110
  {"role": "assistant", "content": ai_response}
111
  ]
112
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
113
 
114
  @app.route('/chat', methods=['POST'])
115
  def chat():
116
- if not model:
117
- return jsonify({"error": "Gemini API not configured"}), 500
118
- msg = request.json.get("message")
119
- if not msg:
120
- return jsonify({"error": "Empty message"}), 400
121
 
122
- if 'project_id' not in session:
123
- session['project_id'] = str(uuid.uuid4())
124
- session['chat_history'] = []
 
 
 
 
125
 
126
- session['chat_history'].append({"role": "user", "parts": [msg]})
127
  try:
128
- chat = model.start_chat(history=session['chat_history'])
129
- reply = chat.send_message(msg).text
130
- session['chat_history'].append({"role": "model", "parts": [reply]})
131
- return jsonify({"reply": reply})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  except Exception as e:
133
- return jsonify({"error": str(e)})
 
134
 
135
  @app.route('/file_tree')
136
  def file_tree():
 
137
  project_id = session.get('project_id')
138
  if not project_id:
139
  return jsonify({"file_tree": []})
140
- path = os.path.join(app.config['UPLOAD_FOLDER'], project_id)
141
- return jsonify({"file_tree": list(get_project_files(path).keys())})
 
 
 
 
142
 
143
  @app.route('/file_content')
144
  def file_content():
 
145
  project_id = session.get('project_id')
146
- path = request.args.get('path')
147
- if not project_id or not path:
148
- return jsonify({"error": "Missing file path"}), 400
149
- full = os.path.join(app.config['UPLOAD_FOLDER'], project_id, path)
 
 
 
 
 
 
 
 
150
  try:
151
- with open(full, 'r', encoding='utf-8', errors='ignore') as f:
152
  return jsonify({"content": f.read()})
 
 
153
  except Exception as e:
154
- return jsonify({"error": str(e)})
155
-
156
  @app.route('/download')
157
  def download():
 
158
  project_id = session.get('project_id')
159
  if not project_id:
160
- return "No project uploaded", 404
161
- path = os.path.join(app.config['UPLOAD_FOLDER'], project_id)
162
- zip_path = os.path.join(app.config['UPLOAD_FOLDER'], f"{project_id}.zip")
163
- with zipfile.ZipFile(zip_path, 'w') as zipf:
164
- for root, _, files in os.walk(path):
 
 
 
 
 
 
 
165
  for file in files:
166
- fpath = os.path.join(root, file)
167
- zipf.write(fpath, os.path.relpath(fpath, path))
168
- return send_from_directory(app.config['UPLOAD_FOLDER'], f"{project_id}.zip", as_attachment=True)
 
 
 
 
 
 
 
169
 
170
  if __name__ == "__main__":
171
- app.run(host='0.0.0.0', port=7860)
 
9
  # Load environment variables
10
  load_dotenv()
11
 
12
+ # --- Gemini AI Setup ---
13
+ # NOTE: It's crucial to use a model that supports file uploads, like Gemini 1.5 Pro.
14
+ # The original model 'gemini-2.0-pro' is not a valid model name.
15
+ # Ensure your GEMINI_API_KEY is set in your .env file.
16
+ gemini_key = os.getenv("GEMINI_API_KEY")
17
+ if gemini_key:
18
+ genai.configure(api_key=gemini_key)
19
+
20
+ # --- Flask App Setup ---
21
  app = Flask(__name__)
22
+ app.config['SECRET_KEY'] = os.getenv("FLASK_SECRET_KEY", "super-secret-key-change-me")
23
  app.config['SESSION_TYPE'] = 'filesystem'
24
  app.config['SESSION_PERMANENT'] = False
25
  app.config['SESSION_FILE_DIR'] = './flask_session'
26
  Session(app)
27
 
28
+ # --- Folder Setup ---
29
  UPLOAD_FOLDER = 'uploads'
30
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
31
  os.makedirs(UPLOAD_FOLDER, exist_ok=True)
32
  os.makedirs(app.config['SESSION_FILE_DIR'], exist_ok=True)
33
 
34
+ def get_project_file_tree(project_path):
35
+ """Generates a list of file paths within a project directory."""
36
+ file_list = []
 
 
 
 
 
 
 
 
 
37
  for root, _, filenames in os.walk(project_path):
38
+ # Ignore hidden files and directories
39
+ if any(part.startswith('.') for part in root.split(os.sep)):
40
+ continue
41
+ for filename in filenames:
42
+ if filename.startswith('.'):
43
+ continue
44
+ # Create a relative path from the project_path base
45
+ rel_path = os.path.relpath(os.path.join(root, filename), project_path)
46
+ file_list.append(rel_path)
47
+ return file_list
48
 
49
  @app.route('/')
50
  def index():
51
+ """Serves the main HTML page."""
52
+ # Initialize session if it's new
53
  if 'project_id' not in session:
54
  session['project_id'] = str(uuid.uuid4())
55
  session['chat_history'] = []
 
57
 
58
  @app.route('/upload', methods=['POST'])
59
  def upload():
60
+ """Handles the upload and initial analysis of a .zip project."""
61
+ if not gemini_key:
62
+ return jsonify({'error': 'Gemini API key is not configured on the server.'}), 500
63
 
64
  if 'project_zip' not in request.files:
65
+ return jsonify({'error': 'No file part in the request.'}), 400
66
+
67
  file = request.files['project_zip']
68
+ if file.filename == '' or not file.filename.endswith('.zip'):
69
+ return jsonify({'error': 'Invalid file. Please upload a .zip file.'}), 400
70
 
71
+ # Ensure a unique project path for the session
72
+ project_id = session.get('project_id', str(uuid.uuid4()))
73
+ session['project_id'] = project_id
74
  project_path = os.path.join(app.config['UPLOAD_FOLDER'], project_id)
75
+
76
+ # Clean up old files if any and recreate directory
77
  if os.path.exists(project_path):
78
+ import shutil
79
+ shutil.rmtree(project_path)
80
+ os.makedirs(project_path)
 
81
 
82
+ # Save and extract the zip file to get the file tree for the UI
83
  zip_path = os.path.join(project_path, file.filename)
84
  file.save(zip_path)
85
+
86
+ try:
87
+ with zipfile.ZipFile(zip_path, 'r') as z:
88
+ z.extractall(project_path)
89
+ except zipfile.BadZipFile:
90
+ return jsonify({'error': 'The uploaded file is not a valid zip archive.'}), 400
 
 
 
91
 
92
+ # --- New Logic: Upload file to Gemini ---
93
  try:
94
+ print("Uploading file to Gemini API...")
95
+ # We upload the saved zip file directly
96
+ project_file = genai.upload_file(path=zip_path, display_name=file.filename)
97
+ print(f"File uploaded successfully. Name: {project_file.name}")
98
+
99
+ # Store the Gemini file reference name in the session
100
+ session['gemini_file_name'] = project_file.name
101
+
102
+ # --- Initial Analysis ---
103
+ model = genai.GenerativeModel(model_name="gemini-1.5-pro-latest")
104
+ prompt = [
105
+ "You are an expert AI code assistant.",
106
+ "Analyze the entire codebase provided in this zip file. Give me a high-level summary of the project, identify potential bugs, suggest improvements or refactoring, and point out any security vulnerabilities.",
107
+ "Structure your response clearly using Markdown.",
108
+ project_file # Pass the file reference here
109
+ ]
110
+ print("Generating initial analysis...")
111
+ response = model.generate_content(prompt)
112
+ ai_response = response.text
113
+
114
+ # Store chat history for the UI
115
+ session['chat_history'] = [
116
+ {"role": "user", "content": "Project uploaded for analysis."},
117
  {"role": "assistant", "content": ai_response}
118
  ]
119
+ session.modified = True
120
+
121
+ # Get file tree for the UI sidebar
122
+ file_tree = get_project_file_tree(project_path)
123
+
124
+ return jsonify({
125
+ "message": "Project uploaded and analyzed.",
126
+ "file_tree": file_tree,
127
+ "chat_history": session['chat_history']
128
+ })
129
+
130
+ except Exception as e:
131
+ print(f"Error during Gemini API call: {e}")
132
+ return jsonify({"error": f"An error occurred while communicating with the AI model: {e}"}), 500
133
+
134
 
135
  @app.route('/chat', methods=['POST'])
136
  def chat():
137
+ """Handles follow-up chat messages, maintaining file context."""
138
+ if not gemini_key:
139
+ return jsonify({"error": "Gemini API key not configured."}), 500
 
 
140
 
141
+ message = request.json.get("message")
142
+ if not message:
143
+ return jsonify({"error": "Empty message received."}), 400
144
+
145
+ gemini_file_name = session.get('gemini_file_name')
146
+ if not gemini_file_name:
147
+ return jsonify({"error": "No project context found. Please upload a project first."}), 400
148
 
 
149
  try:
150
+ # Re-instantiate the file object from the stored name
151
+ project_file = genai.get_file(name=gemini_file_name)
152
+
153
+ # --- Use the same model and construct the prompt with history ---
154
+ model = genai.GenerativeModel(model_name="gemini-2.0-flash")
155
+
156
+ # We don't need to use start_chat. We can pass the file context with each call.
157
+ prompt = [
158
+ message,
159
+ project_file # Pass the file reference with every message
160
+ ]
161
+
162
+ response = model.generate_content(prompt)
163
+
164
+ # Update and save chat history
165
+ if 'chat_history' not in session:
166
+ session['chat_history'] = []
167
+ session['chat_history'].append({"role": "user", "content": message})
168
+ session['chat_history'].append({"role": "assistant", "content": response.text})
169
+ session.modified = True
170
+
171
+ return jsonify({"reply": response.text})
172
+
173
  except Exception as e:
174
+ print(f"Error during Gemini chat: {e}")
175
+ return jsonify({"error": str(e)}), 500
176
 
177
  @app.route('/file_tree')
178
  def file_tree():
179
+ """Returns the file structure of the uploaded project."""
180
  project_id = session.get('project_id')
181
  if not project_id:
182
  return jsonify({"file_tree": []})
183
+
184
+ project_path = os.path.join(app.config['UPLOAD_FOLDER'], project_id)
185
+ if not os.path.exists(project_path):
186
+ return jsonify({"file_tree": []})
187
+
188
+ return jsonify({"file_tree": get_project_file_tree(project_path)})
189
 
190
  @app.route('/file_content')
191
  def file_content():
192
+ """Returns the content of a specific file."""
193
  project_id = session.get('project_id')
194
+ file_path = request.args.get('path')
195
+
196
+ if not project_id or not file_path:
197
+ return jsonify({"error": "Missing project or file path."}), 400
198
+
199
+ # Security: Prevent directory traversal attacks
200
+ base_path = os.path.abspath(os.path.join(app.config['UPLOAD_FOLDER'], project_id))
201
+ full_path = os.path.abspath(os.path.join(base_path, file_path))
202
+
203
+ if not full_path.startswith(base_path):
204
+ return jsonify({"error": "Access denied."}), 403
205
+
206
  try:
207
+ with open(full_path, 'r', encoding='utf-8', errors='ignore') as f:
208
  return jsonify({"content": f.read()})
209
+ except FileNotFoundError:
210
+ return jsonify({"error": "File not found."}), 404
211
  except Exception as e:
212
+ return jsonify({"error": str(e)}), 500
213
+
214
  @app.route('/download')
215
  def download():
216
+ """Allows the user to download the current state of the project as a zip."""
217
  project_id = session.get('project_id')
218
  if not project_id:
219
+ return "No project found in session.", 404
220
+
221
+ project_path = os.path.join(app.config['UPLOAD_FOLDER'], project_id)
222
+ zip_filename = f"{project_id}.zip"
223
+ zip_path = os.path.join(app.config['UPLOAD_FOLDER'], zip_filename)
224
+
225
+ with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
226
+ for root, _, files in os.walk(project_path):
227
+ # Do not include the original uploaded zip file in the new zip
228
+ if os.path.basename(root) == project_id and any(f.endswith('.zip') for f in files):
229
+ files = [f for f in files if not f.endswith('.zip')]
230
+
231
  for file in files:
232
+ file_full_path = os.path.join(root, file)
233
+ # Write file with a relative path inside the zip
234
+ zipf.write(file_full_path, os.path.relpath(file_full_path, project_path))
235
+
236
+ return send_from_directory(
237
+ app.config['UPLOAD_FOLDER'],
238
+ zip_filename,
239
+ as_attachment=True,
240
+ download_name=f"project_{project_id}.zip" # Set a more user-friendly download name
241
+ )
242
 
243
  if __name__ == "__main__":
244
+ app.run(host='0.0.0.0', port=7860)