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"),
|