Spaces:
Paused
Paused
Update app.py via AI Editor
Browse files
app.py
CHANGED
|
@@ -32,14 +32,24 @@ MAX_OUTPUT_TOKENS = 65536
|
|
| 32 |
|
| 33 |
SESSION_STORE = {}
|
| 34 |
|
| 35 |
-
def
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
return sid
|
| 44 |
|
| 45 |
def get_session_data(session_id):
|
|
@@ -471,6 +481,9 @@ def get_proposals_list(proposaldict):
|
|
| 471 |
|
| 472 |
app.layout = dbc.Container([
|
| 473 |
dcc.Store(id='preview-window-state', data='expanded'),
|
|
|
|
|
|
|
|
|
|
| 474 |
dbc.Row([
|
| 475 |
dbc.Col([
|
| 476 |
dbc.Card([
|
|
@@ -568,6 +581,27 @@ app.layout = dbc.Container([
|
|
| 568 |
], style={'marginTop':'20px'})
|
| 569 |
], fluid=True)
|
| 570 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 571 |
@app.callback(
|
| 572 |
Output('output-preview-container', 'style'),
|
| 573 |
[Input('preview-window-state', 'data')]
|
|
@@ -616,7 +650,8 @@ def update_preview_window_style(state):
|
|
| 616 |
State('select-proposal-dropdown', 'value'),
|
| 617 |
State('chat-input', 'value'),
|
| 618 |
Input('cancel-action-btn', 'n_clicks'),
|
| 619 |
-
State('preview-window-state', 'data')
|
|
|
|
| 620 |
],
|
| 621 |
prevent_initial_call=True
|
| 622 |
)
|
|
@@ -624,10 +659,12 @@ def master_callback(
|
|
| 624 |
shred_clicks, proposal_clicks, compliance_clicks, recover_clicks, board_clicks, loe_clicks,
|
| 625 |
rfp_content, rfp_filename, doc_delete_clicks, selected_doc,
|
| 626 |
proposal_content, proposal_filename, proposal_delete_clicks, selected_proposal,
|
| 627 |
-
chat_input, cancel_clicks, preview_window_state
|
|
|
|
| 628 |
):
|
| 629 |
-
session_id
|
| 630 |
-
|
|
|
|
| 631 |
ctx = callback_context
|
| 632 |
triggered_id = ctx.triggered[0]['prop_id'].split('.')[0] if ctx.triggered else None
|
| 633 |
|
|
@@ -669,9 +706,9 @@ def master_callback(
|
|
| 669 |
sess_data["uploaded_documents_bytes"][rfp_filename] = decoded
|
| 670 |
if fileid:
|
| 671 |
sess_data["uploaded_documents_fileid"][rfp_filename] = fileid
|
| 672 |
-
logging.info(f"[{
|
| 673 |
else:
|
| 674 |
-
logging.error(f"[{
|
| 675 |
|
| 676 |
if triggered_id == 'upload-proposal' and proposal_content is not None and proposal_filename:
|
| 677 |
content_type, content_string = proposal_content.split(',')
|
|
@@ -684,9 +721,9 @@ def master_callback(
|
|
| 684 |
sess_data["proposals"][proposal_filename] = text
|
| 685 |
if fileid:
|
| 686 |
sess_data["proposals_fileid"][proposal_filename] = fileid
|
| 687 |
-
logging.info(f"[{
|
| 688 |
else:
|
| 689 |
-
logging.error(f"[{
|
| 690 |
|
| 691 |
if triggered_id and isinstance(doc_delete_clicks, list):
|
| 692 |
for i, n_click in enumerate(doc_delete_clicks):
|
|
@@ -699,14 +736,14 @@ def master_callback(
|
|
| 699 |
try:
|
| 700 |
genai.delete_file(sess_data["uploaded_documents_fileid"][del_filename])
|
| 701 |
except Exception as e:
|
| 702 |
-
logging.warning(f"[{
|
| 703 |
del sess_data["uploaded_documents_fileid"][del_filename]
|
| 704 |
if del_filename in sess_data["uploaded_documents_bytes"]:
|
| 705 |
del sess_data["uploaded_documents_bytes"][del_filename]
|
| 706 |
-
logging.info(f"[{
|
| 707 |
if del_filename in sess_data["shredded_documents"]:
|
| 708 |
del sess_data["shredded_documents"][del_filename]
|
| 709 |
-
logging.info(f"[{
|
| 710 |
if selected_doc == del_filename:
|
| 711 |
selected_doc = None
|
| 712 |
break
|
|
@@ -722,9 +759,9 @@ def master_callback(
|
|
| 722 |
try:
|
| 723 |
genai.delete_file(sess_data["proposals_fileid"][del_filename])
|
| 724 |
except Exception as e:
|
| 725 |
-
logging.warning(f"[{
|
| 726 |
del sess_data["proposals_fileid"][del_filename]
|
| 727 |
-
logging.info(f"[{
|
| 728 |
if selected_proposal == del_filename:
|
| 729 |
selected_proposal = None
|
| 730 |
break
|
|
|
|
| 32 |
|
| 33 |
SESSION_STORE = {}
|
| 34 |
|
| 35 |
+
def get_session_id_from_cookie(cookie_str):
|
| 36 |
+
# Parse a cookie string like "dash_session=abcd; something=xyz"
|
| 37 |
+
if not cookie_str:
|
| 38 |
+
return None
|
| 39 |
+
for part in cookie_str.split(";"):
|
| 40 |
+
if part.strip().startswith("dash_session="):
|
| 41 |
+
return part.strip().split("=")[1]
|
| 42 |
+
return None
|
| 43 |
+
|
| 44 |
+
def get_session_id(session_id=None):
|
| 45 |
+
# Always require session_id as input; never generate unless absent
|
| 46 |
+
if session_id and session_id in SESSION_STORE:
|
| 47 |
+
return session_id
|
| 48 |
+
if session_id:
|
| 49 |
+
# New session, not yet in store
|
| 50 |
+
return session_id
|
| 51 |
+
# Defensive fallback: generate a new session_id (should never hit this)
|
| 52 |
+
sid = str(uuid.uuid4())
|
| 53 |
return sid
|
| 54 |
|
| 55 |
def get_session_data(session_id):
|
|
|
|
| 481 |
|
| 482 |
app.layout = dbc.Container([
|
| 483 |
dcc.Store(id='preview-window-state', data='expanded'),
|
| 484 |
+
dcc.Store(id='session-id-store', storage_type='session'), # Session ID per browser session
|
| 485 |
+
html.Div(id='set-session-cookie', style={'display': 'none'}), # dummy div for JS callback
|
| 486 |
+
dcc.Location(id='dummy-url', refresh=False), # For JS init on page load
|
| 487 |
dbc.Row([
|
| 488 |
dbc.Col([
|
| 489 |
dbc.Card([
|
|
|
|
| 581 |
], style={'marginTop':'20px'})
|
| 582 |
], fluid=True)
|
| 583 |
|
| 584 |
+
# JS callback: Set session cookie and session-id-store on first page load if not present
|
| 585 |
+
app.clientside_callback(
|
| 586 |
+
"""
|
| 587 |
+
function(n, dummy_url) {
|
| 588 |
+
let sid = window.sessionStorage.getItem('dash_session');
|
| 589 |
+
let store_val = null;
|
| 590 |
+
if(!sid) {
|
| 591 |
+
sid = ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
|
| 592 |
+
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
|
| 593 |
+
);
|
| 594 |
+
window.sessionStorage.setItem('dash_session', sid);
|
| 595 |
+
}
|
| 596 |
+
document.cookie = "dash_session=" + sid + "; path=/";
|
| 597 |
+
store_val = sid;
|
| 598 |
+
return store_val;
|
| 599 |
+
}
|
| 600 |
+
""",
|
| 601 |
+
Output('session-id-store', 'data'),
|
| 602 |
+
Input('dummy-url', 'pathname')
|
| 603 |
+
)
|
| 604 |
+
|
| 605 |
@app.callback(
|
| 606 |
Output('output-preview-container', 'style'),
|
| 607 |
[Input('preview-window-state', 'data')]
|
|
|
|
| 650 |
State('select-proposal-dropdown', 'value'),
|
| 651 |
State('chat-input', 'value'),
|
| 652 |
Input('cancel-action-btn', 'n_clicks'),
|
| 653 |
+
State('preview-window-state', 'data'),
|
| 654 |
+
State('session-id-store', 'data')
|
| 655 |
],
|
| 656 |
prevent_initial_call=True
|
| 657 |
)
|
|
|
|
| 659 |
shred_clicks, proposal_clicks, compliance_clicks, recover_clicks, board_clicks, loe_clicks,
|
| 660 |
rfp_content, rfp_filename, doc_delete_clicks, selected_doc,
|
| 661 |
proposal_content, proposal_filename, proposal_delete_clicks, selected_proposal,
|
| 662 |
+
chat_input, cancel_clicks, preview_window_state,
|
| 663 |
+
session_id
|
| 664 |
):
|
| 665 |
+
# Always get session_id from dcc.Store, never generate
|
| 666 |
+
sid = get_session_id(session_id)
|
| 667 |
+
sess_data = get_session_data(sid)
|
| 668 |
ctx = callback_context
|
| 669 |
triggered_id = ctx.triggered[0]['prop_id'].split('.')[0] if ctx.triggered else None
|
| 670 |
|
|
|
|
| 706 |
sess_data["uploaded_documents_bytes"][rfp_filename] = decoded
|
| 707 |
if fileid:
|
| 708 |
sess_data["uploaded_documents_fileid"][rfp_filename] = fileid
|
| 709 |
+
logging.info(f"[{sid}] Document uploaded: {rfp_filename}")
|
| 710 |
else:
|
| 711 |
+
logging.error(f"[{sid}] Failed to decode uploaded document: {rfp_filename}")
|
| 712 |
|
| 713 |
if triggered_id == 'upload-proposal' and proposal_content is not None and proposal_filename:
|
| 714 |
content_type, content_string = proposal_content.split(',')
|
|
|
|
| 721 |
sess_data["proposals"][proposal_filename] = text
|
| 722 |
if fileid:
|
| 723 |
sess_data["proposals_fileid"][proposal_filename] = fileid
|
| 724 |
+
logging.info(f"[{sid}] Proposal uploaded: {proposal_filename}")
|
| 725 |
else:
|
| 726 |
+
logging.error(f"[{sid}] Failed to decode uploaded proposal: {proposal_filename}")
|
| 727 |
|
| 728 |
if triggered_id and isinstance(doc_delete_clicks, list):
|
| 729 |
for i, n_click in enumerate(doc_delete_clicks):
|
|
|
|
| 736 |
try:
|
| 737 |
genai.delete_file(sess_data["uploaded_documents_fileid"][del_filename])
|
| 738 |
except Exception as e:
|
| 739 |
+
logging.warning(f"[{sid}] Failed to delete Gemini file {del_filename}: {e}")
|
| 740 |
del sess_data["uploaded_documents_fileid"][del_filename]
|
| 741 |
if del_filename in sess_data["uploaded_documents_bytes"]:
|
| 742 |
del sess_data["uploaded_documents_bytes"][del_filename]
|
| 743 |
+
logging.info(f"[{sid}] Document deleted: {del_filename}")
|
| 744 |
if del_filename in sess_data["shredded_documents"]:
|
| 745 |
del sess_data["shredded_documents"][del_filename]
|
| 746 |
+
logging.info(f"[{sid}] Shredded doc deleted: {del_filename}")
|
| 747 |
if selected_doc == del_filename:
|
| 748 |
selected_doc = None
|
| 749 |
break
|
|
|
|
| 759 |
try:
|
| 760 |
genai.delete_file(sess_data["proposals_fileid"][del_filename])
|
| 761 |
except Exception as e:
|
| 762 |
+
logging.warning(f"[{sid}] Failed to delete Gemini proposal file {del_filename}: {e}")
|
| 763 |
del sess_data["proposals_fileid"][del_filename]
|
| 764 |
+
logging.info(f"[{sid}] Proposal deleted: {del_filename}")
|
| 765 |
if selected_proposal == del_filename:
|
| 766 |
selected_proposal = None
|
| 767 |
break
|