File size: 9,193 Bytes
8c1a9c9
9759c17
 
 
 
8c1a9c9
9759c17
 
 
 
 
 
 
 
 
 
8c1a9c9
9759c17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8c1a9c9
8a7ffa1
8c1a9c9
9759c17
 
 
 
 
 
8c1a9c9
 
9759c17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8c1a9c9
9759c17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8a7ffa1
9759c17
 
8c1a9c9
9759c17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8c1a9c9
9759c17
 
8c1a9c9
9759c17
 
 
8c1a9c9
9759c17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8c1a9c9
9759c17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8c1a9c9
 
acddd99
1
2
3
4
5
6
7
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
import os
import zipfile
import uuid
import google.generativeai as genai
from flask import Flask, request, jsonify, render_template, session, send_from_directory

# --- Gemini API Configuration ---
# Load the API key from the .env file
from dotenv import load_dotenv
load_dotenv()

# Configure the Gemini API client
genai.configure(api_key=os.environ.get("GEMINI_API_KEY"))
model = genai.GenerativeModel('gemini-1.5-flash') # Or another suitable model

# Initialize the Flask app
app = Flask(__name__)

# Configure the upload folder
UPLOAD_FOLDER = 'uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

if not os.path.exists(UPLOAD_FOLDER):
    os.makedirs(UPLOAD_FOLDER)

def get_project_files(project_path):
    """
    Gets the file tree and content of all files in the project directory.
    """
    file_data = {}
    for root, _, files in os.walk(project_path):
        for file in files:
            file_path = os.path.join(root, file)
            relative_path = os.path.relpath(file_path, project_path)
            try:
                with open(file_path, 'r', encoding='utf-8') as f:
                    content = f.read()
                file_data[relative_path] = content
            except Exception as e:
                file_data[relative_path] = f"Error reading file: {e}"
    return file_data

@app.route('/')
def index():
    """
    Renders the main page.
    """
    if 'project_id' not in session:
        session['project_id'] = str(uuid.uuid4())
        session['chat_history'] = []
    return render_template('index.html')

@app.route('/upload', methods=['POST'])
def upload_project():
    """
    Handles the project zip file upload.
    """
    if 'project_zip' not in request.files:
        return jsonify({"error": "No file part"}), 400
    file = request.files['project_zip']
    if file.filename == '':
        return jsonify({"error": "No selected file"}), 400
    if file and file.filename.endswith('.zip'):
        project_id = session['project_id']
        project_path = os.path.join(app.config['UPLOAD_FOLDER'], project_id)
        if not os.path.exists(project_path):
            os.makedirs(project_path)

        zip_path = os.path.join(project_path, file.filename)
        file.save(zip_path)

        with zipfile.ZipFile(zip_path, 'r') as zip_ref:
            zip_ref.extractall(project_path)
        os.remove(zip_path)

        project_files = get_project_files(project_path)
        session['project_files'] = project_files

        # Initial context for the AI
        initial_context = "The user has uploaded a new project. Here are the files and their content:\n\n"
        for path, content in project_files.items():
            initial_context += f"**File:** `{path}`\n```\n{content}\n```\n\n"

        # Initialize chat history for Gemini
        session['chat_history'] = [
            {"role": "user", "parts": [initial_context]},
            {"role": "model", "parts": ["I have understood the project structure and content. How can I help you improve it?"]}
        ]

        # Prepare response for the frontend (needs 'role' and 'content' keys)
        frontend_chat_history = [
            {"role": "user", "content": initial_context},
            {"role": "assistant", "content": "I have understood the project structure and content. How can I help you improve it?"}
        ]

        return jsonify({
            "message": "Project uploaded and understood.",
            "file_tree": list(project_files.keys()),
            "chat_history": frontend_chat_history
        })
    return jsonify({"error": "Invalid file type, please upload a .zip file"}), 400

@app.route('/chat', methods=['POST'])
def chat():
    """
    Handles the chat interaction with the AI.
    """
    user_message = request.json.get('message')
    if not user_message:
        return jsonify({"error": "Empty message"}), 400

    project_id = session.get('project_id')
    if not project_id:
        return jsonify({"error": "No project uploaded"}), 400

    project_path = os.path.join(app.config['UPLOAD_FOLDER'], project_id)

    # Add user message to chat history
    session['chat_history'].append({"role": "user", "parts": [user_message]})
    session.modified = True

    # Start a chat session with the model using the history
    chat_session = model.start_chat(history=session['chat_history'])

    try:
        # System instruction for Gemini
        system_instruction = """You are an expert programmer AI assistant. When the user asks you to make changes to the code, you must respond with the file path and the complete, updated code for that file within ```python ... ```, ```html ... ```, or other appropriate markdown code blocks. For creating a new file, respond with the new file path and the content. For deleting a file, respond with 'DELETE: [file_path]'. For suggesting improvements with diffs, provide the original and suggested code in separate blocks.
        Example for modifying a file:
        `app/main.py`
        ```python
        # updated python code here
        ```
        Example for creating a file:
        `static/css/new_style.css`
        ```css
        /* new css code */
        ```
        """
        # We combine the system instruction with the user message for better context
        full_prompt = f"{system_instruction}\n\nUser Question: {user_message}"

        response = chat_session.send_message(full_prompt)
        ai_response = response.text

        # Add AI response to chat history
        session['chat_history'].append({"role": "model", "parts": [ai_response]})
        session.modified = True


        # --- AI File Operation Logic (remains the same) ---
        if "```" in ai_response:
            lines = ai_response.split('\n')
            file_path_line = lines[0].strip()
            if file_path_line.startswith('`') and file_path_line.endswith('`'):
                file_path = file_path_line.strip('`')
                try:
                    content_start = ai_response.find('```') + 3
                    content_end = ai_response.rfind('```')
                    # Skip the language hint (e.g., python, html)
                    code_content = ai_response[content_start:].split('\n', 1)[1]
                    code_content = code_content[:content_end - content_start - len(lines[1]) - 1]

                    full_path = os.path.join(project_path, file_path)
                    os.makedirs(os.path.dirname(full_path), exist_ok=True)
                    with open(full_path, 'w', encoding='utf-8') as f:
                        f.write(code_content)
                except IndexError:
                    pass # Or handle error where code block is malformed

        elif ai_response.startswith("DELETE:"):
            file_to_delete = ai_response.split(":", 1)[1].strip()
            full_path = os.path.join(project_path, file_to_delete)
            if os.path.exists(full_path):
                os.remove(full_path)

        return jsonify({"reply": ai_response})

    except Exception as e:
        return jsonify({"error": str(e)}), 500


# --- Other routes (/file_tree, /file_content, /download) remain unchanged ---
# (You can copy them directly from the previous response)

@app.route('/file_tree')
def get_file_tree():
    project_id = session.get('project_id')
    if not project_id:
        return jsonify({"error": "No project uploaded"}), 400
    project_path = os.path.join(app.config['UPLOAD_FOLDER'], project_id)
    if not os.path.exists(project_path):
        return jsonify({"error": "Project not found"}), 404
    file_tree = list(get_project_files(project_path).keys())
    return jsonify({"file_tree": file_tree})


@app.route('/file_content')
def get_file_content():
    project_id = session.get('project_id')
    file_path = request.args.get('path')
    if not project_id or not file_path:
        return jsonify({"error": "Missing project or file path"}), 400

    full_path = os.path.join(app.config['UPLOAD_FOLDER'], project_id, file_path)
    if not os.path.exists(full_path):
        return jsonify({"error": "File not found"}), 404

    try:
        with open(full_path, 'r', encoding='utf-8') as f:
            content = f.read()
        return jsonify({"path": file_path, "content": content})
    except Exception as e:
        return jsonify({"error": str(e)}), 500


@app.route('/download')
def download_project():
    """
    Zips and sends the current project state for download.
    """
    project_id = session.get('project_id')
    if not project_id:
        return "No project to download", 404

    project_path = os.path.join(app.config['UPLOAD_FOLDER'], project_id)
    zip_path = os.path.join(app.config['UPLOAD_FOLDER'], f"{project_id}.zip")

    with zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
        for root, _, files in os.walk(project_path):
            for file in files:
                file_path = os.path.join(root, file)
                arcname = os.path.relpath(file_path, project_path)
                zipf.write(file_path, arcname)

    return send_from_directory(app.config['UPLOAD_FOLDER'], f"{project_id}.zip", as_attachment=True)


if __name__ == '__main__':
    app.run(host="0.0.0.0", port=7860)