Athspi commited on
Commit
8b2be7b
·
verified ·
1 Parent(s): ed70ccf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +96 -194
app.py CHANGED
@@ -1,244 +1,146 @@
1
  import os
2
  import zipfile
3
  import uuid
4
- import google.generativeai as genai
5
  from flask import Flask, request, jsonify, render_template, session, send_from_directory
6
  from flask_session import Session
7
  from dotenv import load_dotenv
 
8
 
9
- # Load environment variables
10
  load_dotenv()
11
 
12
- # Initialize Flask app
13
  app = Flask(__name__)
14
- app.config["SECRET_KEY"] = os.environ.get("FLASK_SECRET_KEY", 'default-insecure-secret-key')
15
-
16
- # Configure server-side sessions
17
- app.config["SESSION_PERMANENT"] = False
18
  app.config["SESSION_TYPE"] = "filesystem"
19
- app.config['SESSION_FILE_DIR'] = './flask_session'
 
20
  Session(app)
21
 
22
- # Configure Gemini AI
23
- try:
24
- gemini_api_key = os.environ.get("GEMINI_API_KEY")
25
- if not gemini_api_key:
26
- print("GEMINI_API_KEY environment variable not found.")
27
- model = None
28
- else:
29
- genai.configure(api_key=gemini_api_key)
30
- model = genai.GenerativeModel('gemini-2.0-flash')
31
- except Exception as e:
32
- print(f"Error configuring Gemini API: {e}")
33
- model = None
34
-
35
- # Configure upload folder
36
  UPLOAD_FOLDER = 'uploads'
37
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
38
  os.makedirs(UPLOAD_FOLDER, exist_ok=True)
39
- os.makedirs('./flask_session', exist_ok=True)
 
 
 
 
 
 
 
40
 
41
  def get_project_files(project_path):
42
- """Get all files in a project directory with their contents."""
43
- file_data = {}
44
- for root, _, files in os.walk(project_path):
45
- for file in files:
46
- if file in ['.DS_Store', '__MACOSX']:
47
- continue
48
- file_path = os.path.join(root, file)
49
- relative_path = os.path.relpath(file_path, project_path)
50
  try:
51
- with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
52
- content = f.read()
53
- file_data[relative_path] = content
54
  except Exception as e:
55
- file_data[relative_path] = f"Error reading file: {e}"
56
- return file_data
57
 
58
  @app.route('/')
59
  def index():
60
- """Render the main application page."""
61
  if 'project_id' not in session:
62
  session['project_id'] = str(uuid.uuid4())
63
  session['chat_history'] = []
64
  return render_template('index.html')
65
 
66
  @app.route('/upload', methods=['POST'])
67
- def upload_project():
68
- """Handle project zip file upload."""
69
- if 'project_id' not in session:
70
- session['project_id'] = str(uuid.uuid4())
71
- session['chat_history'] = []
72
-
73
  if 'project_zip' not in request.files:
74
- return jsonify({"error": "No file part"}), 400
75
-
76
  file = request.files['project_zip']
77
- if file.filename == '':
78
- return jsonify({"error": "No selected file"}), 400
79
-
80
- if file and file.filename.endswith('.zip'):
81
- project_id = session['project_id']
82
- project_path = os.path.join(app.config['UPLOAD_FOLDER'], project_id)
83
-
84
- # Clear existing project files
85
- if os.path.exists(project_path):
86
- for root, dirs, files in os.walk(project_path, topdown=False):
87
- for name in files:
88
- os.remove(os.path.join(root, name))
89
- for name in dirs:
90
- os.rmdir(os.path.join(root, name))
91
-
92
- os.makedirs(project_path, exist_ok=True)
93
- zip_path = os.path.join(project_path, file.filename)
94
- file.save(zip_path)
95
-
96
- with zipfile.ZipFile(zip_path, 'r') as zip_ref:
97
- zip_ref.extractall(project_path)
98
- os.remove(zip_path)
99
-
100
- project_files = get_project_files(project_path)
101
-
102
- # Prepare initial context for AI
103
- initial_context = "The user has uploaded a new project. Here is the file structure and content:\n\n"
104
- for path, content in project_files.items():
105
- initial_context += f"**File:** `{path}`\n```\n{content}\n```\n\n"
106
-
107
- # Update session with chat history
108
- session['chat_history'] = [
109
- {"role": "user", "parts": [initial_context]},
110
- {"role": "model", "parts": ["I have analyzed the project. I am ready to help. How can I assist you with your code?"]}
 
 
 
111
  ]
112
- session.modified = True
113
-
114
- # Prepare response for frontend
115
- return jsonify({
116
- "message": "Project uploaded and analyzed.",
117
- "file_tree": list(project_files.keys()),
118
- "chat_history": [
119
- {"role": "user", "content": "Project context sent to AI."},
120
- {"role": "assistant", "content": "I have analyzed the project. I am ready to help. How can I assist you with your code?"}
121
- ]
122
- })
123
- return jsonify({"error": "Invalid file type. Please upload a .zip file."}), 400
124
 
125
  @app.route('/chat', methods=['POST'])
126
  def chat():
127
- """Handle chat messages with the AI."""
128
  if not model:
129
- return jsonify({"error": "Gemini API is not configured. Please check your API key."}), 500
130
-
131
- user_message = request.json.get('message')
132
- if not user_message:
133
  return jsonify({"error": "Empty message"}), 400
134
-
135
- project_id = session.get('project_id')
136
- if not project_id:
137
- return jsonify({"error": "Session expired. Please upload your project again."}), 400
138
-
139
- project_path = os.path.join(app.config['UPLOAD_FOLDER'], project_id)
140
- session['chat_history'].append({"role": "user", "parts": [user_message]})
141
- session.modified = True
142
-
143
  try:
144
- chat_session = model.start_chat(history=session['chat_history'])
145
- response = chat_session.send_message(user_message)
146
- ai_response = response.text
147
-
148
- session['chat_history'].append({"role": "model", "parts": [ai_response]})
149
- session.modified = True
150
-
151
- # Handle file modifications from AI response
152
- if "```" in ai_response:
153
- lines = ai_response.split('\n')
154
- file_path_line = lines[0].strip()
155
- if file_path_line.startswith('`') and file_path_line.endswith('`'):
156
- file_path = file_path_line.strip('`')
157
- try:
158
- content_start = ai_response.find('```')
159
- code_block = ai_response[content_start:]
160
- code_content = '\n'.join(code_block.split('\n')[1:-1])
161
- full_path = os.path.join(project_path, file_path)
162
- os.makedirs(os.path.dirname(full_path), exist_ok=True)
163
- with open(full_path, 'w', encoding='utf-8') as f:
164
- f.write(code_content)
165
- except Exception:
166
- pass
167
- elif ai_response.strip().startswith("DELETE:"):
168
- file_to_delete = ai_response.strip().split(":", 1)[1].strip()
169
- full_path = os.path.join(project_path, file_to_delete)
170
- if os.path.exists(full_path):
171
- try:
172
- os.remove(full_path)
173
- except OSError as e:
174
- print(f"Error deleting file {full_path}: {e}")
175
-
176
- return jsonify({"reply": ai_response})
177
  except Exception as e:
178
  return jsonify({"error": str(e)}), 500
179
 
180
  @app.route('/file_tree')
181
- def get_file_tree():
182
- """Get the current project's file tree."""
183
- project_id = session.get('project_id')
184
- if not project_id:
185
- return jsonify({"file_tree": []})
186
-
187
- project_path = os.path.join(app.config['UPLOAD_FOLDER'], project_id)
188
- if not os.path.exists(project_path):
189
- return jsonify({"file_tree": []})
190
-
191
- try:
192
- project_files = get_project_files(project_path)
193
- return jsonify({"file_tree": list(project_files.keys())})
194
- except Exception as e:
195
  return jsonify({"file_tree": []})
 
 
196
 
197
  @app.route('/file_content')
198
- def get_file_content():
199
- """Get the content of a specific file."""
200
- project_id = session.get('project_id')
201
- file_path = request.args.get('path')
202
-
203
- if not project_id or not file_path:
204
- return jsonify({"error": "No file selected"}), 400
205
-
206
- full_path = os.path.join(app.config['UPLOAD_FOLDER'], project_id, file_path)
207
- if not os.path.exists(full_path):
208
- return jsonify({"error": "File not found"}), 404
209
-
210
  try:
211
- with open(full_path, 'r', encoding='utf-8', errors='ignore') as f:
212
- content = f.read()
213
- return jsonify({"content": content})
214
  except Exception as e:
215
- return jsonify({"error": str(e)}), 500
216
 
217
  @app.route('/download')
218
- def download_project():
219
- """Download the current project as a zip file."""
220
- project_id = session.get('project_id')
221
- if not project_id:
222
- return "No project to download. Please upload a project first.", 404
223
-
224
- project_path = os.path.join(app.config['UPLOAD_FOLDER'], project_id)
225
- if not os.path.exists(project_path):
226
- return "Project files not found.", 404
227
-
228
- zip_filename = f"project_{project_id}.zip"
229
- zip_path = os.path.join(app.config['UPLOAD_FOLDER'], zip_filename)
230
-
231
- if os.path.exists(zip_path):
232
- os.remove(zip_path)
233
-
234
- with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
235
- for root, _, files in os.walk(project_path):
236
  for file in files:
237
- file_path = os.path.join(root, file)
238
- arcname = os.path.relpath(file_path, project_path)
239
- zipf.write(file_path, arcname)
240
-
241
- return send_from_directory(app.config['UPLOAD_FOLDER'], zip_filename, as_attachment=True)
242
-
243
- if __name__ == '__main__':
244
- app.run(host="0.0.0.0", port=7860)
 
1
  import os
2
  import zipfile
3
  import uuid
 
4
  from flask import Flask, request, jsonify, render_template, session, send_from_directory
5
  from flask_session import Session
6
  from dotenv import load_dotenv
7
+ import google.generativeai as genai
8
 
9
+ # Load env
10
  load_dotenv()
11
 
 
12
  app = Flask(__name__)
13
+ app.config["SECRET_KEY"] = os.getenv("FLASK_SECRET_KEY", "insecure")
 
 
 
14
  app.config["SESSION_TYPE"] = "filesystem"
15
+ app.config["SESSION_FILE_DIR"] = './flask_session'
16
+ app.config["SESSION_PERMANENT"] = False
17
  Session(app)
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  UPLOAD_FOLDER = 'uploads'
20
  app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
21
  os.makedirs(UPLOAD_FOLDER, exist_ok=True)
22
+ os.makedirs(app.config['SESSION_FILE_DIR'], exist_ok=True)
23
+
24
+ # Configure Gemini
25
+ gemini_api_key = os.getenv("GEMINI_API_KEY")
26
+ model = None
27
+ if gemini_api_key:
28
+ genai.configure(api_key=gemini_api_key)
29
+ model = genai.GenerativeModel('gemini-2.0-flash')
30
 
31
  def get_project_files(project_path):
32
+ files = {}
33
+ for root, _, filenames in os.walk(project_path):
34
+ for file in filenames:
35
+ if file.startswith('.'): continue
36
+ rel_path = os.path.relpath(os.path.join(root, file), project_path)
 
 
 
37
  try:
38
+ with open(os.path.join(root, file), 'r', encoding='utf-8', errors='ignore') as f:
39
+ files[rel_path] = f.read()
 
40
  except Exception as e:
41
+ files[rel_path] = f"Error reading: {e}"
42
+ return files
43
 
44
  @app.route('/')
45
  def index():
 
46
  if 'project_id' not in session:
47
  session['project_id'] = str(uuid.uuid4())
48
  session['chat_history'] = []
49
  return render_template('index.html')
50
 
51
  @app.route('/upload', methods=['POST'])
52
+ def upload():
 
 
 
 
 
53
  if 'project_zip' not in request.files:
54
+ return jsonify({'error': 'No file uploaded'}), 400
 
55
  file = request.files['project_zip']
56
+ if not file.filename.endswith('.zip'):
57
+ return jsonify({'error': 'Only .zip supported'}), 400
58
+
59
+ project_id = session['project_id']
60
+ project_path = os.path.join(app.config['UPLOAD_FOLDER'], project_id)
61
+ if os.path.exists(project_path):
62
+ for root, dirs, files in os.walk(project_path, topdown=False):
63
+ for f in files:
64
+ os.remove(os.path.join(root, f))
65
+ for d in dirs:
66
+ os.rmdir(os.path.join(root, d))
67
+ os.makedirs(project_path, exist_ok=True)
68
+
69
+ zip_path = os.path.join(project_path, file.filename)
70
+ file.save(zip_path)
71
+ with zipfile.ZipFile(zip_path, 'r') as z:
72
+ z.extractall(project_path)
73
+ os.remove(zip_path)
74
+
75
+ files = get_project_files(project_path)
76
+
77
+ context = "User uploaded a project:\n"
78
+ for path, content in files.items():
79
+ context += f"**File:** `{path}`\n```\n{content[:2000]}\n```\n\n" # clip long content
80
+
81
+ session['chat_history'] = [
82
+ {"role": "user", "parts": [context]},
83
+ {"role": "model", "parts": ["Project loaded. Ready to help."]}
84
+ ]
85
+ session.modified = True
86
+
87
+ return jsonify({
88
+ "message": "Project uploaded.",
89
+ "file_tree": list(files.keys()),
90
+ "chat_history": [
91
+ {"role": "user", "content": "Project context sent to AI."},
92
+ {"role": "assistant", "content": "Project loaded. Ready to help."}
93
  ]
94
+ })
 
 
 
 
 
 
 
 
 
 
 
95
 
96
  @app.route('/chat', methods=['POST'])
97
  def chat():
 
98
  if not model:
99
+ return jsonify({"error": "Gemini API not configured"}), 500
100
+ msg = request.json.get("message")
101
+ if not msg:
 
102
  return jsonify({"error": "Empty message"}), 400
103
+ session['chat_history'].append({"role": "user", "parts": [msg]})
 
 
 
 
 
 
 
 
104
  try:
105
+ chat = model.start_chat(history=session['chat_history'])
106
+ reply = chat.send_message(msg).text
107
+ session['chat_history'].append({"role": "model", "parts": [reply]})
108
+ return jsonify({"reply": reply})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  except Exception as e:
110
  return jsonify({"error": str(e)}), 500
111
 
112
  @app.route('/file_tree')
113
+ def file_tree():
114
+ pid = session.get('project_id')
115
+ if not pid:
 
 
 
 
 
 
 
 
 
 
 
116
  return jsonify({"file_tree": []})
117
+ path = os.path.join(app.config['UPLOAD_FOLDER'], pid)
118
+ return jsonify({"file_tree": list(get_project_files(path).keys())})
119
 
120
  @app.route('/file_content')
121
+ def file_content():
122
+ pid = session.get('project_id')
123
+ filepath = request.args.get('path')
124
+ full = os.path.join(app.config['UPLOAD_FOLDER'], pid, filepath)
 
 
 
 
 
 
 
 
125
  try:
126
+ with open(full, 'r', encoding='utf-8', errors='ignore') as f:
127
+ return jsonify({"content": f.read()})
 
128
  except Exception as e:
129
+ return jsonify({"error": str(e)})
130
 
131
  @app.route('/download')
132
+ def download():
133
+ pid = session.get('project_id')
134
+ if not pid:
135
+ return "No project uploaded", 404
136
+ path = os.path.join(app.config['UPLOAD_FOLDER'], pid)
137
+ zpath = os.path.join(app.config['UPLOAD_FOLDER'], f"{pid}.zip")
138
+ with zipfile.ZipFile(zpath, 'w') as zipf:
139
+ for root, _, files in os.walk(path):
 
 
 
 
 
 
 
 
 
 
140
  for file in files:
141
+ fpath = os.path.join(root, file)
142
+ zipf.write(fpath, os.path.relpath(fpath, path))
143
+ return send_from_directory(app.config['UPLOAD_FOLDER'], f"{pid}.zip", as_attachment=True)
144
+
145
+ if __name__ == "__main__":
146
+ app.run(host='0.0.0.0', port=7860)