Update app.py via AI Editor
Browse files
app.py
CHANGED
@@ -273,99 +273,54 @@ app.layout = dbc.Container([
|
|
273 |
|
274 |
@app.callback(
|
275 |
Output('file-list', 'children'),
|
276 |
-
Input('upload-files', 'contents'),
|
277 |
-
|
278 |
-
|
|
|
279 |
)
|
280 |
-
def
|
281 |
-
session_data, lock = get_session_data()
|
282 |
-
logger.info("Uploading files...")
|
283 |
-
if list_of_contents is not None:
|
284 |
-
with lock:
|
285 |
-
for content, name in zip(list_of_contents, list_of_names):
|
286 |
-
content_type, content_string = content.split(',')
|
287 |
-
decoded = base64.b64decode(content_string)
|
288 |
-
temp_path = os.path.join(session_data['temp_dir'], name)
|
289 |
-
with open(temp_path, 'wb') as f:
|
290 |
-
f.write(decoded)
|
291 |
-
session_data['uploaded_files'][name] = temp_path
|
292 |
-
session_data['file_texts'][name] = parse_file_content(temp_path, name)
|
293 |
-
logger.info(f"Files after upload: {list(session_data['uploaded_files'].keys())}")
|
294 |
-
return get_file_cards(session_data['uploaded_files'])
|
295 |
-
raise PreventUpdate
|
296 |
-
|
297 |
-
@app.callback(
|
298 |
-
Output('file-list', 'children'),
|
299 |
-
Input({'type': 'remove-file', 'index': ALL}, 'n_clicks'),
|
300 |
-
State('file-list', 'children'),
|
301 |
-
prevent_initial_call=True
|
302 |
-
)
|
303 |
-
def remove_file(n_clicks, file_list_children):
|
304 |
ctx = callback_context
|
305 |
-
if not ctx.triggered or not any(n_clicks):
|
306 |
-
raise PreventUpdate
|
307 |
-
session_data, lock = get_session_data()
|
308 |
-
triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]
|
309 |
-
import ast
|
310 |
-
try:
|
311 |
-
triggered_id_dict = ast.literal_eval(triggered_id)
|
312 |
-
removed_file = triggered_id_dict['index']
|
313 |
-
except Exception:
|
314 |
-
raise PreventUpdate
|
315 |
-
with lock:
|
316 |
-
if removed_file in session_data['uploaded_files']:
|
317 |
-
try:
|
318 |
-
os.remove(session_data['uploaded_files'][removed_file])
|
319 |
-
logger.info(f"Deleted file from disk: {removed_file}")
|
320 |
-
except Exception as e:
|
321 |
-
logger.warning(f"Failed to delete temp file {removed_file}: {e}")
|
322 |
-
session_data['uploaded_files'].pop(removed_file, None)
|
323 |
-
session_data['file_texts'].pop(removed_file, None)
|
324 |
-
logger.info(f"Files after deletion: {list(session_data['uploaded_files'].keys())}")
|
325 |
-
return get_file_cards(session_data['uploaded_files'])
|
326 |
-
|
327 |
-
@app.callback(
|
328 |
-
Output('file-list', 'children', allow_duplicate=True),
|
329 |
-
Input('file-list', 'children'),
|
330 |
-
prevent_initial_call=False
|
331 |
-
)
|
332 |
-
def restore_file_list(file_list_children):
|
333 |
session_data, lock = get_session_data()
|
|
|
334 |
with lock:
|
335 |
restore_session_files(session_data)
|
336 |
-
|
337 |
-
|
338 |
-
|
339 |
-
|
340 |
-
|
341 |
-
|
342 |
-
|
343 |
-
)
|
344 |
-
|
345 |
-
|
346 |
-
|
347 |
-
|
348 |
-
|
349 |
-
|
350 |
-
|
351 |
-
|
352 |
-
|
353 |
-
|
354 |
-
|
355 |
-
raise PreventUpdate
|
356 |
-
|
357 |
-
if not session_data['uploaded_files']:
|
358 |
-
return html.Div("Please upload project artifacts before generating a matrix."), ""
|
359 |
-
file_contents = list(session_data['file_texts'].values())
|
360 |
-
with lock:
|
361 |
try:
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
369 |
|
370 |
def generate_matrix_with_gpt(matrix_type, file_contents):
|
371 |
prompt = f"""Generate a {matrix_type} based on the following project artifacts:
|
@@ -401,19 +356,52 @@ Now, generate the {matrix_type}:
|
|
401 |
return pd.DataFrame(data, columns=headers)
|
402 |
|
403 |
@app.callback(
|
|
|
|
|
404 |
Output('chat-output', 'children'),
|
405 |
-
|
406 |
-
Input('btn-send-chat', 'n_clicks'),
|
407 |
-
State('chat-input', 'value'),
|
408 |
-
prevent_initial_call=
|
409 |
)
|
410 |
-
def
|
411 |
session_data, lock = get_session_data()
|
412 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
413 |
raise PreventUpdate
|
414 |
-
|
415 |
-
|
416 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
417 |
Current matrix:
|
418 |
{session_data['current_matrix'].to_string(index=False)}
|
419 |
Instructions:
|
@@ -426,22 +414,24 @@ Instructions:
|
|
426 |
7. Each subsequent row should represent a single item in the matrix.
|
427 |
Now, provide the updated {matrix_type}:
|
428 |
"""
|
429 |
-
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
|
444 |
-
|
|
|
|
|
445 |
|
446 |
@app.callback(
|
447 |
Output("download-matrix", "data"),
|
|
|
273 |
|
274 |
@app.callback(
|
275 |
Output('file-list', 'children'),
|
276 |
+
[Input('upload-files', 'contents'),
|
277 |
+
Input({'type': 'remove-file', 'index': ALL}, 'n_clicks')],
|
278 |
+
[State('upload-files', 'filename')],
|
279 |
+
prevent_initial_call='initial_duplicate'
|
280 |
)
|
281 |
+
def handle_file_upload_and_remove(list_of_contents, n_clicks, list_of_names):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
282 |
ctx = callback_context
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
283 |
session_data, lock = get_session_data()
|
284 |
+
# On first load, restore file list from session temp dir
|
285 |
with lock:
|
286 |
restore_session_files(session_data)
|
287 |
+
# If upload triggered
|
288 |
+
if ctx.triggered and ctx.triggered[0]['prop_id'].startswith("upload-files.contents"):
|
289 |
+
logger.info("Uploading files...")
|
290 |
+
if list_of_contents is not None:
|
291 |
+
with lock:
|
292 |
+
for content, name in zip(list_of_contents, list_of_names):
|
293 |
+
content_type, content_string = content.split(',')
|
294 |
+
decoded = base64.b64decode(content_string)
|
295 |
+
temp_path = os.path.join(session_data['temp_dir'], name)
|
296 |
+
with open(temp_path, 'wb') as f:
|
297 |
+
f.write(decoded)
|
298 |
+
session_data['uploaded_files'][name] = temp_path
|
299 |
+
session_data['file_texts'][name] = parse_file_content(temp_path, name)
|
300 |
+
logger.info(f"Files after upload: {list(session_data['uploaded_files'].keys())}")
|
301 |
+
return get_file_cards(session_data['uploaded_files'])
|
302 |
+
# If remove triggered
|
303 |
+
elif ctx.triggered and "remove-file" in ctx.triggered[0]['prop_id']:
|
304 |
+
triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]
|
305 |
+
import ast
|
|
|
|
|
|
|
|
|
|
|
|
|
306 |
try:
|
307 |
+
triggered_id_dict = ast.literal_eval(triggered_id)
|
308 |
+
removed_file = triggered_id_dict['index']
|
309 |
+
except Exception:
|
310 |
+
raise PreventUpdate
|
311 |
+
with lock:
|
312 |
+
if removed_file in session_data['uploaded_files']:
|
313 |
+
try:
|
314 |
+
os.remove(session_data['uploaded_files'][removed_file])
|
315 |
+
logger.info(f"Deleted file from disk: {removed_file}")
|
316 |
+
except Exception as e:
|
317 |
+
logger.warning(f"Failed to delete temp file {removed_file}: {e}")
|
318 |
+
session_data['uploaded_files'].pop(removed_file, None)
|
319 |
+
session_data['file_texts'].pop(removed_file, None)
|
320 |
+
logger.info(f"Files after deletion: {list(session_data['uploaded_files'].keys())}")
|
321 |
+
return get_file_cards(session_data['uploaded_files'])
|
322 |
+
# Otherwise just display current files (e.g. on initial load)
|
323 |
+
return get_file_cards(session_data['uploaded_files'])
|
324 |
|
325 |
def generate_matrix_with_gpt(matrix_type, file_contents):
|
326 |
prompt = f"""Generate a {matrix_type} based on the following project artifacts:
|
|
|
356 |
return pd.DataFrame(data, columns=headers)
|
357 |
|
358 |
@app.callback(
|
359 |
+
Output('matrix-preview', 'children'),
|
360 |
+
Output('loading-output', 'children'),
|
361 |
Output('chat-output', 'children'),
|
362 |
+
[Input({'type': 'matrix-btn', 'index': matrix_label}, 'n_clicks') for matrix_label in matrix_types.keys()] +
|
363 |
+
[Input('btn-send-chat', 'n_clicks')],
|
364 |
+
[State('chat-input', 'value')],
|
365 |
+
prevent_initial_call='initial_duplicate'
|
366 |
)
|
367 |
+
def handle_matrix_and_chat(*args):
|
368 |
session_data, lock = get_session_data()
|
369 |
+
ctx = callback_context
|
370 |
+
# Matrix generation button pressed
|
371 |
+
matrix_btns_len = len(matrix_types)
|
372 |
+
matrix_btn_inputs = args[:matrix_btns_len]
|
373 |
+
chat_n_clicks = args[matrix_btns_len]
|
374 |
+
chat_input_value = args[matrix_btns_len + 1]
|
375 |
+
if not ctx.triggered:
|
376 |
raise PreventUpdate
|
377 |
+
triggered_id = ctx.triggered[0]['prop_id'].split('.')[0]
|
378 |
+
# If matrix button triggered
|
379 |
+
if "matrix-btn" in triggered_id:
|
380 |
+
import ast
|
381 |
+
try:
|
382 |
+
triggered = ast.literal_eval(triggered_id)
|
383 |
+
matrix_type = triggered['index']
|
384 |
+
except Exception:
|
385 |
+
raise PreventUpdate
|
386 |
+
if not session_data['uploaded_files']:
|
387 |
+
return html.Div("Please upload project artifacts before generating a matrix."), "", ""
|
388 |
+
file_contents = list(session_data['file_texts'].values())
|
389 |
+
with lock:
|
390 |
+
try:
|
391 |
+
session_data['matrix_type'] = matrix_type
|
392 |
+
session_data['current_matrix'] = generate_matrix_with_gpt(matrix_type, file_contents)
|
393 |
+
logger.info(f"{matrix_type} generated for session.")
|
394 |
+
return dbc.Table.from_dataframe(session_data['current_matrix'], striped=True, bordered=True, hover=True), f"{matrix_type} generated", ""
|
395 |
+
except Exception as e:
|
396 |
+
logger.exception(f"Error generating matrix: {str(e)}")
|
397 |
+
return html.Div(f"Error generating matrix: {str(e)}"), "Error", ""
|
398 |
+
# If chat send button triggered
|
399 |
+
elif "btn-send-chat" in triggered_id:
|
400 |
+
if not chat_input_value or session_data['current_matrix'] is None or session_data['matrix_type'] is None:
|
401 |
+
raise PreventUpdate
|
402 |
+
matrix_type = session_data['matrix_type']
|
403 |
+
with lock:
|
404 |
+
prompt = f"""Update the following {matrix_type} based on this instruction: {chat_input_value}
|
405 |
Current matrix:
|
406 |
{session_data['current_matrix'].to_string(index=False)}
|
407 |
Instructions:
|
|
|
414 |
7. Each subsequent row should represent a single item in the matrix.
|
415 |
Now, provide the updated {matrix_type}:
|
416 |
"""
|
417 |
+
response = openai.ChatCompletion.create(
|
418 |
+
model="gpt-4-turbo",
|
419 |
+
messages=[
|
420 |
+
{"role": "system", "content": "You are a precise matrix updater that outputs only the requested matrix without any additional text. You will make assumptions as a project manager to produce the matrix based on the limited information provided"},
|
421 |
+
{"role": "user", "content": prompt}
|
422 |
+
]
|
423 |
+
)
|
424 |
+
updated_matrix_text = response.choices[0].message.content.strip()
|
425 |
+
logger.info(f"Raw updated matrix text from GPT: {updated_matrix_text[:200]}...")
|
426 |
+
lines = [line.strip() for line in updated_matrix_text.split('\n') if '|' in line]
|
427 |
+
data = [line.split('|') for line in lines]
|
428 |
+
data = [[cell.strip() for cell in row] for row in data]
|
429 |
+
headers = data[0]
|
430 |
+
data = data[1:]
|
431 |
+
session_data['current_matrix'] = pd.DataFrame(data, columns=headers)
|
432 |
+
return dbc.Table.from_dataframe(session_data['current_matrix'], striped=True, bordered=True, hover=True), "", f"Matrix updated based on: {chat_input_value}"
|
433 |
+
else:
|
434 |
+
raise PreventUpdate
|
435 |
|
436 |
@app.callback(
|
437 |
Output("download-matrix", "data"),
|