bluenevus commited on
Commit
947d651
·
1 Parent(s): 307700a

Update app.py via AI Editor

Browse files
Files changed (1) hide show
  1. app.py +154 -72
app.py CHANGED
@@ -8,7 +8,9 @@ import pandas as pd
8
  import logging
9
  from docx import Document
10
  import mimetypes
11
- from threading import Thread
 
 
12
 
13
  import google.generativeai as genai
14
 
@@ -36,6 +38,11 @@ shredded_documents = {}
36
  shredded_document = None
37
  generated_response = None
38
 
 
 
 
 
 
39
  def decode_document(decoded_bytes):
40
  try:
41
  content = decoded_bytes.decode('utf-8')
@@ -84,7 +91,8 @@ def upload_to_gemini_file(decoded_bytes, filename):
84
  logging.error(f"Exception during file upload to Gemini: {e}")
85
  return None
86
 
87
- def gemini_generate_content(prompt, file_id=None, chat_input=None):
 
88
  try:
89
  files = []
90
  if file_id:
@@ -99,6 +107,7 @@ def gemini_generate_content(prompt, file_id=None, chat_input=None):
99
  content_list.append("\n\n")
100
  content_list.append(prompt)
101
  model = genai.GenerativeModel(GEMINI_MODEL)
 
102
  response = model.generate_content(
103
  contents=content_list,
104
  generation_config=genai.types.GenerationConfig(
@@ -106,11 +115,19 @@ def gemini_generate_content(prompt, file_id=None, chat_input=None):
106
  )
107
  )
108
  result = response.text if hasattr(response, "text") else str(response)
109
- logging.info("Received response from Gemini.")
110
- return result
 
 
 
 
 
 
 
 
111
  except Exception as e:
112
  logging.error("Error during Gemini generate_content: %s", e)
113
- return f"Error during Gemini completion: {e}"
114
 
115
  def save_shredded_as_docx(shredded_text, rfp_filename):
116
  doc = Document()
@@ -132,8 +149,8 @@ def save_proposal_as_docx(proposal_text, base_filename):
132
  memf.seek(0)
133
  return memf.read()
134
 
135
- def process_document(action, selected_filename=None, chat_input=None, rfp_decoded_bytes=None):
136
- global shredded_document, generated_response
137
  logging.info(f"Process document called with action: {action}")
138
 
139
  doc_content = None
@@ -163,24 +180,36 @@ def process_document(action, selected_filename=None, chat_input=None, rfp_decode
163
  prompt += f"User additional instructions: {chat_input}\n"
164
  prompt += f"\nFile Name: {selected_filename}\n\n"
165
  result_holder = {"text": None, "docx_name": None}
 
166
  def thread_shred():
167
- global shredded_document
168
  shredded_document = ""
 
169
  try:
170
  logging.info("Starting Gemini generate_content for shredding.")
171
- result = gemini_generate_content(prompt, file_id=doc_fileid, chat_input=chat_input)
 
 
 
 
172
  shredded_document = result
 
 
 
 
 
 
 
 
 
173
  logging.info("Document shredded successfully.")
174
- docx_bytes = save_shredded_as_docx(result, selected_filename)
175
- generated_docx_name = f"{os.path.splitext(selected_filename)[0]}_shredded.docx"
176
- shredded_documents[generated_docx_name] = docx_bytes
177
- result_holder["text"] = result
178
- result_holder["docx_name"] = generated_docx_name
179
  except Exception as e:
180
  shredded_document = f"Error during shredding: {e}"
181
  logging.error("Error in thread_shred: %s", e)
182
  result_holder["text"] = shredded_document
 
183
  shredded_document = "Shredding in progress..."
 
184
  t = Thread(target=thread_shred)
185
  t.start()
186
  t.join()
@@ -216,23 +245,35 @@ def process_document(action, selected_filename=None, chat_input=None, rfp_decode
216
  prompt += f"\n---\nRFP/SOW/PWS/RFI ({rfp_filename}):\n{doc_content}\n"
217
  logging.info(f"Sending proposal prompt to Gemini. RFP: {rfp_filename}")
218
  result_holder = {"text": None, "docx_name": None}
 
219
  def thread_proposal():
220
- global generated_response
221
  generated_response = ""
 
222
  try:
223
  logging.info("Connecting to Gemini for proposal.")
224
- response = gemini_generate_content(prompt, file_id=rfp_fileid, chat_input=chat_input)
 
 
 
 
225
  generated_response = response
 
 
 
 
 
 
 
 
 
226
  logging.info("Received proposal results from Gemini.")
227
- docx_bytes = save_proposal_as_docx(response, rfp_filename)
228
- generated_docx_name = f"{os.path.splitext(rfp_filename)[0]}_proposal.docx"
229
- generated_documents[generated_docx_name] = docx_bytes
230
- result_holder["text"] = response
231
- result_holder["docx_name"] = generated_docx_name
232
  except Exception as e:
233
  generated_response = f"Error during Gemini completion: {e}"
234
  logging.error("Error during Gemini proposal request: %s", e)
235
  result_holder["text"] = generated_response
 
 
236
  t = Thread(target=thread_proposal)
237
  t.start()
238
  t.join()
@@ -254,20 +295,16 @@ def get_uploaded_doc_list(docdict):
254
  doc_list = []
255
  for filename in docdict:
256
  file_content = docdict[filename]
257
- # Always encode as docx for download, but if not docx, encode as txt
258
  if filename.lower().endswith('.docx'):
259
  mime = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
260
  b64 = None
261
- # docx as text, not as file: re-encode to docx for download
262
  try:
263
- # If it's a docx, but only text available, save as docx
264
  docx_bytes = save_shredded_as_docx(file_content, filename)
265
  b64 = base64.b64encode(docx_bytes).decode('utf-8')
266
  except Exception:
267
  b64 = base64.b64encode(file_content.encode('utf-8')).decode('utf-8')
268
  else:
269
  mime = "text/plain"
270
- # Always encode as utf-8 text for download
271
  b64 = base64.b64encode(file_content.encode('utf-8')).decode('utf-8')
272
  download_link = html.A(
273
  filename,
@@ -380,6 +417,8 @@ def get_generated_rfp_download_section(selected_value):
380
  app.layout = dbc.Container([
381
  dcc.Store(id='shred-store', data={'text': None, 'docx_name': None}),
382
  dcc.Store(id='proposal-store', data={'text': None, 'docx_name': None}),
 
 
383
  dbc.Row([
384
  dbc.Col([
385
  dbc.Card([
@@ -482,6 +521,7 @@ app.layout = dbc.Container([
482
  dbc.Button("Recover", id="recover-action-btn", className="me-3 mb-2 btn-tertiary"),
483
  dbc.Button("Virtual Board", id="board-action-btn", className="me-3 mb-2 btn-tertiary"),
484
  dbc.Button("LOE", id="loe-action-btn", className="mb-2 btn-tertiary"),
 
485
  ], className="mt-3 mb-3 d-flex flex-wrap"),
486
  dcc.Loading(
487
  id="loading",
@@ -507,6 +547,8 @@ app.layout = dbc.Container([
507
  Output('select-generated-dropdown', 'options'),
508
  Output('select-generated-dropdown', 'value'),
509
  Output('generated-rfp-section', 'children'),
 
 
510
  [
511
  Input('shred-action-btn', 'n_clicks'),
512
  Input('proposal-action-btn', 'n_clicks'),
@@ -527,6 +569,7 @@ app.layout = dbc.Container([
527
  State('select-document-dropdown', 'value'),
528
  State('select-proposal-dropdown', 'value'),
529
  State('select-generated-dropdown', 'value'),
 
530
  ],
531
  prevent_initial_call=True
532
  )
@@ -537,7 +580,8 @@ def master_callback(
537
  generated_delete_clicks, selected_generated,
538
  shredded_delete_clicks, shredded_doc_children,
539
  select_generated_value,
540
- chat_input, selected_filename, selected_proposal_dropdown, selected_generated_dropdown_state
 
541
  ):
542
  ctx = callback_context
543
  triggered_id = ctx.triggered[0]['prop_id'].split('.')[0] if ctx.triggered else None
@@ -552,6 +596,7 @@ def master_callback(
552
 
553
  shred_store = {'text': None, 'docx_name': None}
554
  proposal_store = {'text': None, 'docx_name': None}
 
555
 
556
  rfp_delete_clicks = safe_get_n_clicks(ctx, 4)
557
  proposal_delete_clicks = safe_get_n_clicks(ctx, 8)
@@ -560,6 +605,30 @@ def master_callback(
560
 
561
  uploaded_rfp_decoded_bytes = None
562
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
563
  if triggered_id == 'upload-document' and rfp_content is not None and rfp_filename:
564
  content_type, content_string = rfp_content.split(',')
565
  decoded = base64.b64decode(content_string)
@@ -666,54 +735,41 @@ def master_callback(
666
 
667
  output_data_upload = html.Div("No action taken yet.", style={"wordWrap": "break-word"})
668
 
669
- if triggered_id == 'shred-action-btn':
670
- output_data_upload = dcc.Loading(type="default", children=html.Div("Shredding document...", style={"wordWrap": "break-word"}), style={"textAlign": "center"})
671
- shred_text, _, shredded_docx_name, shredded_text = process_document('shred', doc_value, chat_input)
672
- shred_store = {'text': shred_text, 'docx_name': shredded_docx_name}
673
-
674
- if shredded_docx_name:
675
- doc_options = [{'label': fn, 'value': fn} for fn in uploaded_documents.keys()]
676
- doc_value = shredded_docx_name if shredded_docx_name in uploaded_documents else (next(iter(uploaded_documents), None) if uploaded_documents else None)
677
- uploaded_doc_list = get_uploaded_doc_list(uploaded_documents)
678
- shredded_doc_list_items = get_shredded_doc_list(shredded_documents)
679
- output_data_upload = dcc.Markdown(shred_text, style={"whiteSpace": "pre-wrap", "wordWrap": "break-word"})
680
- generated_rfp_section = get_generated_rfp_download_section(generated_doc_value)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
681
  return (
682
  shred_store, proposal_store, output_data_upload,
683
  uploaded_doc_list, doc_options, doc_value,
684
  shredded_doc_list_items,
685
  generated_doc_list, generated_doc_options, generated_doc_value,
686
- generated_rfp_section
687
- )
688
-
689
- if triggered_id == 'proposal-action-btn':
690
- output_data_upload = dcc.Loading(type="default", children=html.Div("Generating proposal...", style={"wordWrap": "break-word"}), style={"textAlign": "center"})
691
- # get decoded bytes for the selected RFP doc for upload if needed
692
- rfp_decoded_bytes = None
693
- if doc_value and doc_value in uploaded_documents:
694
- # Try to get from upload-document if that was just uploaded
695
- if rfp_content is not None and rfp_filename == doc_value:
696
- content_type, content_string = rfp_content.split(',')
697
- rfp_decoded_bytes = base64.b64decode(content_string)
698
- # otherwise, not available, unless user re-uploads
699
- proposal_text, _, proposal_docx_name, proposal_text_preview = process_document('proposal', doc_value, chat_input, rfp_decoded_bytes)
700
- proposal_store = {'text': proposal_text, 'docx_name': proposal_docx_name}
701
- new_generated_doc_value = generated_doc_value
702
- if proposal_docx_name:
703
- generated_doc_options = [{'label': fn, 'value': fn} for fn in generated_documents.keys()]
704
- new_generated_doc_value = proposal_docx_name if proposal_docx_name in generated_documents else (next(iter(generated_documents), None) if generated_documents else None)
705
- generated_doc_list = get_generated_doc_list(generated_documents)
706
- generated_rfp_section = get_generated_rfp_download_section(new_generated_doc_value)
707
- logging.info(f"Generated proposal docx saved: {proposal_docx_name}")
708
- else:
709
- generated_rfp_section = get_generated_rfp_download_section(generated_doc_value)
710
- output_data_upload = dcc.Markdown(proposal_text, style={"whiteSpace": "pre-wrap", "wordWrap": "break-word"})
711
- return (
712
- shred_store, proposal_store, output_data_upload,
713
- uploaded_doc_list, doc_options, doc_value,
714
- shredded_doc_list_items,
715
- generated_doc_list, generated_doc_options, new_generated_doc_value,
716
- generated_rfp_section
717
  )
718
 
719
  if triggered_id == 'select-generated-dropdown':
@@ -723,7 +779,9 @@ def master_callback(
723
  uploaded_doc_list, doc_options, doc_value,
724
  shredded_doc_list_items,
725
  generated_doc_list, generated_doc_options, generated_doc_value,
726
- generated_rfp_section
 
 
727
  )
728
 
729
  if upload_triggered:
@@ -736,7 +794,9 @@ def master_callback(
736
  uploaded_doc_list, doc_options, doc_value,
737
  shredded_doc_list_items,
738
  generated_doc_list, generated_doc_options, generated_doc_value,
739
- generated_rfp_section
 
 
740
  )
741
 
742
  doc_value = doc_value if doc_value in uploaded_documents else (next(iter(uploaded_documents), None) if uploaded_documents else None)
@@ -747,9 +807,31 @@ def master_callback(
747
  uploaded_doc_list, doc_options, doc_value,
748
  shredded_doc_list_items,
749
  generated_doc_list, generated_doc_options, generated_doc_value,
750
- generated_rfp_section
 
 
751
  )
752
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
753
  if __name__ == '__main__':
754
  print("Starting the Dash application...")
755
  app.run(debug=True, host='0.0.0.0', port=7860, threaded=True)
 
8
  import logging
9
  from docx import Document
10
  import mimetypes
11
+ from threading import Thread, Lock, Event
12
+ import threading
13
+ import time
14
 
15
  import google.generativeai as genai
16
 
 
38
  shredded_document = None
39
  generated_response = None
40
 
41
+ # Streaming and cancel globals
42
+ gemini_lock = Lock()
43
+ stream_buffer = {"preview": ""} # Only one operation at a time
44
+ stream_event = Event()
45
+
46
  def decode_document(decoded_bytes):
47
  try:
48
  content = decoded_bytes.decode('utf-8')
 
91
  logging.error(f"Exception during file upload to Gemini: {e}")
92
  return None
93
 
94
+ def gemini_generate_content_stream(prompt, file_id=None, chat_input=None, cancel_event=None):
95
+ # Streaming Gemini not officially supported, so simulate by splitting result into manageable chunks
96
  try:
97
  files = []
98
  if file_id:
 
107
  content_list.append("\n\n")
108
  content_list.append(prompt)
109
  model = genai.GenerativeModel(GEMINI_MODEL)
110
+ # No streaming in SDK, so emulate
111
  response = model.generate_content(
112
  contents=content_list,
113
  generation_config=genai.types.GenerationConfig(
 
115
  )
116
  )
117
  result = response.text if hasattr(response, "text") else str(response)
118
+ chunk_size = 400 # chars per chunk
119
+ for i in range(0, len(result), chunk_size):
120
+ if cancel_event is not None and cancel_event.is_set():
121
+ logging.info("Gemini stream cancelled by user.")
122
+ yield "[Cancelled by user]\n"
123
+ break
124
+ yield result[:i+chunk_size]
125
+ time.sleep(0.08)
126
+ if cancel_event is not None and cancel_event.is_set():
127
+ yield "[Cancelled by user]\n"
128
  except Exception as e:
129
  logging.error("Error during Gemini generate_content: %s", e)
130
+ yield f"Error during Gemini completion: {e}"
131
 
132
  def save_shredded_as_docx(shredded_text, rfp_filename):
133
  doc = Document()
 
149
  memf.seek(0)
150
  return memf.read()
151
 
152
+ def process_document(action, selected_filename=None, chat_input=None, rfp_decoded_bytes=None, cancel_event=None, stream=True):
153
+ global shredded_document, generated_response, stream_buffer
154
  logging.info(f"Process document called with action: {action}")
155
 
156
  doc_content = None
 
180
  prompt += f"User additional instructions: {chat_input}\n"
181
  prompt += f"\nFile Name: {selected_filename}\n\n"
182
  result_holder = {"text": None, "docx_name": None}
183
+
184
  def thread_shred():
185
+ global shredded_document, stream_buffer
186
  shredded_document = ""
187
+ stream_buffer["preview"] = ""
188
  try:
189
  logging.info("Starting Gemini generate_content for shredding.")
190
+ for partial in gemini_generate_content_stream(prompt, file_id=doc_fileid, chat_input=chat_input, cancel_event=cancel_event):
191
+ stream_buffer["preview"] = partial
192
+ if cancel_event is not None and cancel_event.is_set():
193
+ break
194
+ result = stream_buffer["preview"]
195
  shredded_document = result
196
+ if not cancel_event or not cancel_event.is_set():
197
+ docx_bytes = save_shredded_as_docx(result, selected_filename)
198
+ generated_docx_name = f"{os.path.splitext(selected_filename)[0]}_shredded.docx"
199
+ shredded_documents[generated_docx_name] = docx_bytes
200
+ result_holder["text"] = result
201
+ result_holder["docx_name"] = generated_docx_name
202
+ else:
203
+ result_holder["text"] = "[Cancelled by user]\n"
204
+ result_holder["docx_name"] = None
205
  logging.info("Document shredded successfully.")
 
 
 
 
 
206
  except Exception as e:
207
  shredded_document = f"Error during shredding: {e}"
208
  logging.error("Error in thread_shred: %s", e)
209
  result_holder["text"] = shredded_document
210
+
211
  shredded_document = "Shredding in progress..."
212
+ stream_buffer["preview"] = ""
213
  t = Thread(target=thread_shred)
214
  t.start()
215
  t.join()
 
245
  prompt += f"\n---\nRFP/SOW/PWS/RFI ({rfp_filename}):\n{doc_content}\n"
246
  logging.info(f"Sending proposal prompt to Gemini. RFP: {rfp_filename}")
247
  result_holder = {"text": None, "docx_name": None}
248
+
249
  def thread_proposal():
250
+ global generated_response, stream_buffer
251
  generated_response = ""
252
+ stream_buffer["preview"] = ""
253
  try:
254
  logging.info("Connecting to Gemini for proposal.")
255
+ for partial in gemini_generate_content_stream(prompt, file_id=rfp_fileid, chat_input=chat_input, cancel_event=cancel_event):
256
+ stream_buffer["preview"] = partial
257
+ if cancel_event is not None and cancel_event.is_set():
258
+ break
259
+ response = stream_buffer["preview"]
260
  generated_response = response
261
+ if not cancel_event or not cancel_event.is_set():
262
+ docx_bytes = save_proposal_as_docx(response, rfp_filename)
263
+ generated_docx_name = f"{os.path.splitext(rfp_filename)[0]}_proposal.docx"
264
+ generated_documents[generated_docx_name] = docx_bytes
265
+ result_holder["text"] = response
266
+ result_holder["docx_name"] = generated_docx_name
267
+ else:
268
+ result_holder["text"] = "[Cancelled by user]\n"
269
+ result_holder["docx_name"] = None
270
  logging.info("Received proposal results from Gemini.")
 
 
 
 
 
271
  except Exception as e:
272
  generated_response = f"Error during Gemini completion: {e}"
273
  logging.error("Error during Gemini proposal request: %s", e)
274
  result_holder["text"] = generated_response
275
+
276
+ stream_buffer["preview"] = ""
277
  t = Thread(target=thread_proposal)
278
  t.start()
279
  t.join()
 
295
  doc_list = []
296
  for filename in docdict:
297
  file_content = docdict[filename]
 
298
  if filename.lower().endswith('.docx'):
299
  mime = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
300
  b64 = None
 
301
  try:
 
302
  docx_bytes = save_shredded_as_docx(file_content, filename)
303
  b64 = base64.b64encode(docx_bytes).decode('utf-8')
304
  except Exception:
305
  b64 = base64.b64encode(file_content.encode('utf-8')).decode('utf-8')
306
  else:
307
  mime = "text/plain"
 
308
  b64 = base64.b64encode(file_content.encode('utf-8')).decode('utf-8')
309
  download_link = html.A(
310
  filename,
 
417
  app.layout = dbc.Container([
418
  dcc.Store(id='shred-store', data={'text': None, 'docx_name': None}),
419
  dcc.Store(id='proposal-store', data={'text': None, 'docx_name': None}),
420
+ dcc.Store(id='stream-status', data={'streaming': False}),
421
+ dcc.Interval(id='stream-interval', interval=500, n_intervals=0, disabled=True),
422
  dbc.Row([
423
  dbc.Col([
424
  dbc.Card([
 
521
  dbc.Button("Recover", id="recover-action-btn", className="me-3 mb-2 btn-tertiary"),
522
  dbc.Button("Virtual Board", id="board-action-btn", className="me-3 mb-2 btn-tertiary"),
523
  dbc.Button("LOE", id="loe-action-btn", className="mb-2 btn-tertiary"),
524
+ dbc.Button("Cancel", id="cancel-action-btn", className="ms-3 mb-2 btn-danger", color="danger"),
525
  ], className="mt-3 mb-3 d-flex flex-wrap"),
526
  dcc.Loading(
527
  id="loading",
 
547
  Output('select-generated-dropdown', 'options'),
548
  Output('select-generated-dropdown', 'value'),
549
  Output('generated-rfp-section', 'children'),
550
+ Output('stream-status', 'data'),
551
+ Output('stream-interval', 'disabled'),
552
  [
553
  Input('shred-action-btn', 'n_clicks'),
554
  Input('proposal-action-btn', 'n_clicks'),
 
569
  State('select-document-dropdown', 'value'),
570
  State('select-proposal-dropdown', 'value'),
571
  State('select-generated-dropdown', 'value'),
572
+ Input('cancel-action-btn', 'n_clicks')
573
  ],
574
  prevent_initial_call=True
575
  )
 
580
  generated_delete_clicks, selected_generated,
581
  shredded_delete_clicks, shredded_doc_children,
582
  select_generated_value,
583
+ chat_input, selected_filename, selected_proposal_dropdown, selected_generated_dropdown_state,
584
+ cancel_clicks
585
  ):
586
  ctx = callback_context
587
  triggered_id = ctx.triggered[0]['prop_id'].split('.')[0] if ctx.triggered else None
 
596
 
597
  shred_store = {'text': None, 'docx_name': None}
598
  proposal_store = {'text': None, 'docx_name': None}
599
+ streaming = False
600
 
601
  rfp_delete_clicks = safe_get_n_clicks(ctx, 4)
602
  proposal_delete_clicks = safe_get_n_clicks(ctx, 8)
 
605
 
606
  uploaded_rfp_decoded_bytes = None
607
 
608
+ global gemini_lock, stream_buffer, stream_event
609
+
610
+ if triggered_id == 'cancel-action-btn':
611
+ stream_event.set()
612
+ streaming = False
613
+ output_data_upload = html.Div("[Cancelled by user]\n", style={"wordWrap": "break-word"})
614
+ doc_options = [{'label': fn, 'value': fn} for fn in uploaded_documents.keys()]
615
+ doc_value = selected_doc if selected_doc in uploaded_documents else (next(iter(uploaded_documents), None) if uploaded_documents else None)
616
+ shredded_doc_list_items = get_shredded_doc_list(shredded_documents)
617
+ uploaded_doc_list = get_uploaded_doc_list(uploaded_documents)
618
+ generated_doc_list = get_generated_doc_list(generated_documents)
619
+ generated_doc_options = [{'label': fn, 'value': fn} for fn in generated_documents.keys()]
620
+ generated_doc_value = select_generated_value if select_generated_value in generated_documents else (next(iter(generated_documents), None) if generated_documents else None)
621
+ generated_rfp_section = get_generated_rfp_download_section(generated_doc_value)
622
+ return (
623
+ shred_store, proposal_store, output_data_upload,
624
+ uploaded_doc_list, doc_options, doc_value,
625
+ shredded_doc_list_items,
626
+ generated_doc_list, generated_doc_options, generated_doc_value,
627
+ generated_rfp_section,
628
+ {'streaming': False},
629
+ True
630
+ )
631
+
632
  if triggered_id == 'upload-document' and rfp_content is not None and rfp_filename:
633
  content_type, content_string = rfp_content.split(',')
634
  decoded = base64.b64decode(content_string)
 
735
 
736
  output_data_upload = html.Div("No action taken yet.", style={"wordWrap": "break-word"})
737
 
738
+ # Stream logic: only one at a time
739
+ if triggered_id in ['shred-action-btn', 'proposal-action-btn']:
740
+ got_lock = gemini_lock.acquire(blocking=False)
741
+ if not got_lock:
742
+ output_data_upload = html.Div("Another Gemini operation is in progress. Please wait or cancel.", style={"wordWrap": "break-word"})
743
+ return (
744
+ shred_store, proposal_store, output_data_upload,
745
+ uploaded_doc_list, doc_options, doc_value,
746
+ shredded_doc_list_items,
747
+ generated_doc_list, generated_doc_options, generated_doc_value,
748
+ generated_rfp_section,
749
+ {'streaming': False},
750
+ True
751
+ )
752
+ stream_event.clear()
753
+ stream_buffer["preview"] = ""
754
+ streaming = True
755
+ # Launch thread and let interval update the preview
756
+ def stream_gemini_thread(action, doc_value, chat_input, rfp_decoded_bytes):
757
+ try:
758
+ process_document(action, doc_value, chat_input, rfp_decoded_bytes, cancel_event=stream_event, stream=True)
759
+ finally:
760
+ gemini_lock.release()
761
+ t = Thread(target=stream_gemini_thread, args=(('shred' if triggered_id=='shred-action-btn' else 'proposal'), doc_value, chat_input, uploaded_rfp_decoded_bytes))
762
+ t.daemon = True
763
+ t.start()
764
+ output_data_upload = dcc.Markdown("Starting Gemini operation...", style={"whiteSpace": "pre-wrap", "wordWrap": "break-word"})
765
  return (
766
  shred_store, proposal_store, output_data_upload,
767
  uploaded_doc_list, doc_options, doc_value,
768
  shredded_doc_list_items,
769
  generated_doc_list, generated_doc_options, generated_doc_value,
770
+ generated_rfp_section,
771
+ {'streaming': True},
772
+ False
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
773
  )
774
 
775
  if triggered_id == 'select-generated-dropdown':
 
779
  uploaded_doc_list, doc_options, doc_value,
780
  shredded_doc_list_items,
781
  generated_doc_list, generated_doc_options, generated_doc_value,
782
+ generated_rfp_section,
783
+ {'streaming': False},
784
+ True
785
  )
786
 
787
  if upload_triggered:
 
794
  uploaded_doc_list, doc_options, doc_value,
795
  shredded_doc_list_items,
796
  generated_doc_list, generated_doc_options, generated_doc_value,
797
+ generated_rfp_section,
798
+ {'streaming': False},
799
+ True
800
  )
801
 
802
  doc_value = doc_value if doc_value in uploaded_documents else (next(iter(uploaded_documents), None) if uploaded_documents else None)
 
807
  uploaded_doc_list, doc_options, doc_value,
808
  shredded_doc_list_items,
809
  generated_doc_list, generated_doc_options, generated_doc_value,
810
+ generated_rfp_section,
811
+ {'streaming': False},
812
+ True
813
  )
814
 
815
+ @app.callback(
816
+ Output('output-data-upload', 'children'),
817
+ Output('stream-status', 'data'),
818
+ Output('stream-interval', 'disabled'),
819
+ Input('stream-interval', 'n_intervals'),
820
+ State('stream-status', 'data'),
821
+ prevent_initial_call=True
822
+ )
823
+ def stream_output_callback(n_intervals, stream_status):
824
+ global stream_buffer, gemini_lock
825
+ if not stream_status.get('streaming'):
826
+ return dash.no_update, stream_status, True
827
+ preview = stream_buffer.get("preview", "")
828
+ # If lock is free, means operation done
829
+ still_streaming = gemini_lock.locked()
830
+ if not still_streaming:
831
+ stream_status['streaming'] = False
832
+ return dcc.Markdown(preview, style={"whiteSpace": "pre-wrap", "wordWrap": "break-word"}), stream_status, True
833
+ return dcc.Markdown(preview, style={"whiteSpace": "pre-wrap", "wordWrap": "break-word"}), stream_status, False
834
+
835
  if __name__ == '__main__':
836
  print("Starting the Dash application...")
837
  app.run(debug=True, host='0.0.0.0', port=7860, threaded=True)