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
|