bluenevus commited on
Commit
83acb90
·
1 Parent(s): 24742cb

Update app.py via AI Editor

Browse files
Files changed (1) hide show
  1. app.py +81 -61
app.py CHANGED
@@ -15,6 +15,7 @@ from PyPDF2 import PdfReader
15
  import logging
16
  import uuid
17
  import xlsxwriter # Needed for Excel export engine
 
18
 
19
  # --- Logging Configuration ---
20
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
@@ -26,12 +27,13 @@ app = dash.Dash(__name__,
26
  external_stylesheets=[dbc.themes.BOOTSTRAP],
27
  suppress_callback_exceptions=True,
28
  meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1"}])
29
- server = app.server
30
 
31
  # --- Configure Gemini AI ---
32
  # IMPORTANT: Set the GEMINI_API_KEY environment variable.
33
  try:
34
- # Prefer direct CUDA GPU configuration in app.py - Note: Not applicable for cloud APIs like Gemini.
 
35
  api_key = os.environ.get("GEMINI_API_KEY")
36
  if not api_key:
37
  logging.warning("GEMINI_API_KEY environment variable not found. AI features will be disabled.")
@@ -39,6 +41,7 @@ try:
39
  else:
40
  genai.configure(api_key=api_key)
41
  # Using 'gemini-1.5-pro-latest' or similar advanced model is recommended.
 
42
  model = genai.GenerativeModel('gemini-2.5-pro-preview-03-25')
43
  logging.info("Gemini AI configured successfully using 'gemini-2.5-pro-preview-03-25'.")
44
  except Exception as e:
@@ -46,30 +49,34 @@ except Exception as e:
46
  model = None
47
 
48
  # --- Global Variables ---
49
- # Consider dcc.Store for more robust multi-user state management.
50
- uploaded_files = {} # {filename: content_text}
 
 
51
 
52
- # Stores the *results* of generation/review steps
53
- shredded_document = None
54
- pink_review_document = None
55
- red_review_document = None
56
- gold_review_document = None
57
- loe_document = None
58
- virtual_board_document = None
59
 
60
- # Stores the *generated* proposal drafts
61
- pink_document = None
62
- red_document = None
63
- gold_document = None
 
 
 
64
 
65
- # Store uploaded content specifically for review inputs
66
- uploaded_pink_content = None
67
- uploaded_red_content = None
68
- uploaded_gold_content = None
69
 
70
- # Store the currently displayed document and its type for download/chat
71
- current_display_document = None
72
- current_display_type = None
 
 
 
 
73
 
74
  # --- Document Types ---
75
  document_types = {
@@ -86,9 +93,10 @@ document_types = {
86
 
87
  # --- Layout Definition ---
88
  app.layout = dbc.Container(fluid=True, className="dbc", children=[
 
89
  # Title Row
90
  dbc.Row(
91
- dbc.Col(html.H1("Proposal AI Assistant", className="text-center my-4", style={'color': '#1C304A'}), width=12)
92
  ),
93
 
94
  # Progress Indicator Row
@@ -96,12 +104,12 @@ app.layout = dbc.Container(fluid=True, className="dbc", children=[
96
  dbc.Col(
97
  dcc.Loading(
98
  id="loading-indicator",
99
- type="dots",
100
- children=[html.Div(id="loading-output", style={'height': '10px'})],
101
- overlay_style={"visibility":"hidden", "opacity": 0},
102
- style={'visibility':'hidden', 'height': '30px'},
 
103
  fullscreen=False,
104
- className="justify-content-center"
105
  ),
106
  width=12,
107
  className="text-center mb-3"
@@ -110,7 +118,7 @@ app.layout = dbc.Container(fluid=True, className="dbc", children=[
110
 
111
  # Main Content Row
112
  dbc.Row([
113
- # Left Column (Nav/Upload)
114
  dbc.Col(
115
  dbc.Card(
116
  dbc.CardBody([
@@ -118,85 +126,94 @@ app.layout = dbc.Container(fluid=True, className="dbc", children=[
118
  dcc.Upload(
119
  id='upload-document',
120
  children=html.Div(['Drag and Drop or ', html.A('Select PWS/Source Files')]),
121
- style={
122
  'width': '100%', 'height': '60px', 'lineHeight': '60px',
123
  'borderWidth': '1px', 'borderStyle': 'dashed', 'borderRadius': '5px',
124
- 'textAlign': 'center', 'margin': '10px 0', 'backgroundColor': '#ffffff'
125
  },
126
  multiple=True
127
  ),
128
- dbc.Card(
129
  dbc.CardBody(
130
  html.Div(id='file-list', style={'maxHeight': '150px', 'overflowY': 'auto', 'fontSize': '0.9em'})
131
- ), className="mb-3" , style={'backgroundColor': '#ffffff'}
132
  ),
133
  html.Hr(),
134
  html.H4("2. Select Action", className="card-title mt-3"),
135
- dbc.Card(
136
  dbc.CardBody([
137
  *[dbc.Button(
138
  doc_type,
139
  id={'type': 'action-button', 'index': doc_type},
140
- color="primary",
141
  className="mb-2 w-100 d-block",
142
- style={'textAlign': 'left', 'whiteSpace': 'normal', 'height': 'auto', 'wordWrap': 'break-word'}
143
  ) for doc_type in document_types.keys()]
144
  ])
145
  )
146
  ])
147
- , color="light"),
148
- width=12, lg=4,
149
  className="mb-3 mb-lg-0",
150
- style={'padding': '15px'}
151
  ),
152
 
153
- # Right Column (Status/Preview/Controls/Chat)
154
  dbc.Col(
155
  dbc.Card(
156
  dbc.CardBody([
157
  dbc.Alert(id='status-bar', children="Upload source documents and select an action.", color="info"),
158
- dbc.Card(id='review-controls-card', children=[dbc.CardBody(id='review-controls')], className="mb-3", style={'display': 'none'}),
159
- dbc.Card(
160
  dbc.CardBody([
161
  html.H5("Document Preview / Output", className="card-title"),
162
  dcc.Loading(
163
  id="loading-preview",
164
  type="circle",
165
- children=[html.Div(id='document-preview', style={'whiteSpace': 'pre-wrap', 'maxHeight': '400px', 'overflowY': 'auto', 'border': '1px solid #ccc', 'padding': '10px', 'borderRadius': '5px', 'background': '#f8f9fa'})]
166
  )
167
  ]), className="mb-3"
168
  ),
169
- dbc.Button("Download Output", id="btn-download", color="success", className="mt-3 me-2", style={'display': 'none'}),
170
  dcc.Download(id="download-document"),
171
  html.Hr(),
172
- dbc.Card(
173
  dbc.CardBody([
174
  html.H5("Refine Output (Chat)", className="card-title"),
175
  dcc.Loading(
176
  id="chat-loading",
177
  type="circle",
178
  children=[
179
- dbc.Textarea(id="chat-input", placeholder="Enter instructions to refine the document shown above...", className="mb-2", style={'whiteSpace': 'normal', 'wordWrap': 'break-word'}),
180
  dbc.ButtonGroup([
181
  dbc.Button("Send Chat", id="btn-send-chat", color="secondary"),
182
  dbc.Button("Clear Chat", id="btn-clear-chat", color="tertiary")
183
  ], className="mb-3"),
184
- html.Div(id="chat-output", style={'whiteSpace': 'pre-wrap', 'marginTop': '10px', 'border': '1px solid #eee', 'padding': '10px', 'borderRadius': '5px', 'minHeight': '50px'})
185
  ]
186
  )
187
  ]), className="mb-3"
188
  )
189
  ])
190
- ),
191
- width=12, lg=8,
192
- style={'backgroundColor': '#ffffff', 'padding': '15px'}
193
  )
194
  ])
195
- ], style={'maxWidth': '100%', 'padding': '0 15px'})
196
 
197
 
198
  # --- Helper Functions ---
199
 
 
 
 
 
 
 
 
 
 
200
  def process_document(contents, filename):
201
  """Processes uploaded file content (PDF or DOCX) and returns text, or None and error message."""
202
  if contents is None:
@@ -239,26 +256,29 @@ def process_document(contents, filename):
239
  logging.error(f"Error processing document {filename}: {e}", exc_info=True)
240
  return None, f"Error processing file {filename}: {str(e)}"
241
 
242
- def get_combined_uploaded_text():
243
- """Combines text content of all successfully uploaded files."""
244
- if not uploaded_files:
245
- return ""
246
- return "\n\n--- FILE BREAK ---\n\n".join(uploaded_files.values())
 
 
247
 
248
- def generate_ai_document(doc_type, input_docs, context_docs=None):
249
- """Generates document using Gemini AI. Updates current_display."""
250
- global current_display_document, current_display_type
251
 
252
  if not model:
253
- logging.error("Gemini AI model not initialized.")
254
  return "Error: AI Model not configured. Please check API Key."
255
  if not input_docs or not any(doc.strip() for doc in input_docs if doc):
256
- logging.warning(f"generate_ai_document called for {doc_type} with no valid input documents.")
257
  return f"Error: Missing required input document(s) for {doc_type} generation."
258
 
259
  combined_input = "\n\n---\n\n".join(filter(None, input_docs))
260
  combined_context = "\n\n---\n\n".join(filter(None, context_docs)) if context_docs else ""
261
 
 
262
  prompt = f"""**Objective:** Generate the '{doc_type}' document.
263
  **Your Role:** Act as an expert proposal writer/analyst.
264
  **Core Instructions:**
 
15
  import logging
16
  import uuid
17
  import xlsxwriter # Needed for Excel export engine
18
+ import threading # For multi-threading
19
 
20
  # --- Logging Configuration ---
21
  logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 
27
  external_stylesheets=[dbc.themes.BOOTSTRAP],
28
  suppress_callback_exceptions=True,
29
  meta_tags=[{"name": "viewport", "content": "width=device-width, initial-scale=1"}])
30
+ server = app.server # Expose server for deployment
31
 
32
  # --- Configure Gemini AI ---
33
  # IMPORTANT: Set the GEMINI_API_KEY environment variable.
34
  try:
35
+ # Prefer direct CUDA GPU configuration in app.py - Note: Not directly applicable for cloud APIs like Gemini.
36
+ # Configuration happens via environment variable or direct API key setting.
37
  api_key = os.environ.get("GEMINI_API_KEY")
38
  if not api_key:
39
  logging.warning("GEMINI_API_KEY environment variable not found. AI features will be disabled.")
 
41
  else:
42
  genai.configure(api_key=api_key)
43
  # Using 'gemini-1.5-pro-latest' or similar advanced model is recommended.
44
+ # Using the user-specified model: gemini-2.5-pro-preview-03-25
45
  model = genai.GenerativeModel('gemini-2.5-pro-preview-03-25')
46
  logging.info("Gemini AI configured successfully using 'gemini-2.5-pro-preview-03-25'.")
47
  except Exception as e:
 
49
  model = None
50
 
51
  # --- Global Variables ---
52
+ # Using dcc.Store for more robust state management is recommended for production,
53
+ # but for simplicity and current scope, using global variables with locks for threading.
54
+ # A lock for thread-safe access to shared global variables
55
+ data_lock = threading.Lock()
56
 
57
+ # {session_id: {filename: content_text}} - Store uploaded files per session
58
+ uploaded_files = {}
 
 
 
 
 
59
 
60
+ # {session_id: document_content} - Stores the *results* of generation/review steps per session
61
+ shredded_document = {}
62
+ pink_review_document = {}
63
+ red_review_document = {}
64
+ gold_review_document = {}
65
+ loe_document = {}
66
+ virtual_board_document = {}
67
 
68
+ # {session_id: document_content} - Stores the *generated* proposal drafts per session
69
+ pink_document = {}
70
+ red_document = {}
71
+ gold_document = {}
72
 
73
+ # {session_id: content_text} - Store uploaded content specifically for review inputs per session
74
+ uploaded_pink_content = {}
75
+ uploaded_red_content = {}
76
+ uploaded_gold_content = {}
77
+
78
+ # {session_id: {'doc': content, 'type': doc_type}} - Store the currently displayed document and its type for download/chat per session
79
+ current_display_document = {}
80
 
81
  # --- Document Types ---
82
  document_types = {
 
93
 
94
  # --- Layout Definition ---
95
  app.layout = dbc.Container(fluid=True, className="dbc", children=[
96
+ dcc.Store(id='session-id', storage_type='session'), # Store for unique session ID
97
  # Title Row
98
  dbc.Row(
99
+ dbc.Col(html.H1("Proposal AI Assistant", className="text-center my-4"), width=12) # Removed inline style
100
  ),
101
 
102
  # Progress Indicator Row
 
104
  dbc.Col(
105
  dcc.Loading(
106
  id="loading-indicator",
107
+ type="dots", # Changed to dots
108
+ children=[html.Div(id="loading-output", style={'height': '10px'})], # Simplified children
109
+ overlay_style={"visibility":"hidden", "opacity": 0}, # Hide default overlay
110
+ style={'visibility':'hidden'}, # Initially hidden
111
+ parent_style={'minHeight': '30px'}, # Ensure space is allocated
112
  fullscreen=False,
 
113
  ),
114
  width=12,
115
  className="text-center mb-3"
 
118
 
119
  # Main Content Row
120
  dbc.Row([
121
+ # Left Column (Nav/Upload) - 30% width on large screens
122
  dbc.Col(
123
  dbc.Card(
124
  dbc.CardBody([
 
126
  dcc.Upload(
127
  id='upload-document',
128
  children=html.Div(['Drag and Drop or ', html.A('Select PWS/Source Files')]),
129
+ style={ # Basic styling, colors/backgrounds handled by CSS
130
  'width': '100%', 'height': '60px', 'lineHeight': '60px',
131
  'borderWidth': '1px', 'borderStyle': 'dashed', 'borderRadius': '5px',
132
+ 'textAlign': 'center', 'margin': '10px 0'
133
  },
134
  multiple=True
135
  ),
136
+ dbc.Card( # Inner card for file list
137
  dbc.CardBody(
138
  html.Div(id='file-list', style={'maxHeight': '150px', 'overflowY': 'auto', 'fontSize': '0.9em'})
139
+ ), className="mb-3" # Removed inline style
140
  ),
141
  html.Hr(),
142
  html.H4("2. Select Action", className="card-title mt-3"),
143
+ dbc.Card( # Inner card for buttons
144
  dbc.CardBody([
145
  *[dbc.Button(
146
  doc_type,
147
  id={'type': 'action-button', 'index': doc_type},
148
+ color="primary", # Use bootstrap classes
149
  className="mb-2 w-100 d-block",
150
+ style={'textAlign': 'left', 'whiteSpace': 'normal', 'height': 'auto', 'wordWrap': 'break-word'} # Style for word wrap
151
  ) for doc_type in document_types.keys()]
152
  ])
153
  )
154
  ])
155
+ , color="light", className="h-100"), # Use bootstrap class for background, ensure full height
156
+ width=12, lg=4, # Full width on small, 4/12 (33%) on large
157
  className="mb-3 mb-lg-0",
158
+ style={'paddingRight': '15px'} # Add padding between columns
159
  ),
160
 
161
+ # Right Column (Status/Preview/Controls/Chat) - 70% width on large screens
162
  dbc.Col(
163
  dbc.Card(
164
  dbc.CardBody([
165
  dbc.Alert(id='status-bar', children="Upload source documents and select an action.", color="info"),
166
+ dbc.Card(id='review-controls-card', children=[dbc.CardBody(id='review-controls')], className="mb-3", style={'display': 'none'}), # Initially hidden review controls
167
+ dbc.Card( # Card for preview
168
  dbc.CardBody([
169
  html.H5("Document Preview / Output", className="card-title"),
170
  dcc.Loading(
171
  id="loading-preview",
172
  type="circle",
173
+ children=[html.Div(id='document-preview', style={'whiteSpace': 'pre-wrap', 'wordWrap': 'break-word', 'maxHeight': '400px', 'overflowY': 'auto', 'border': '1px solid #ccc', 'padding': '10px', 'borderRadius': '5px'})] # Added wordWrap
174
  )
175
  ]), className="mb-3"
176
  ),
177
+ dbc.Button("Download Output", id="btn-download", color="success", className="mt-3 me-2", style={'display': 'none'}), # Initially hidden download
178
  dcc.Download(id="download-document"),
179
  html.Hr(),
180
+ dbc.Card( # Card for chat
181
  dbc.CardBody([
182
  html.H5("Refine Output (Chat)", className="card-title"),
183
  dcc.Loading(
184
  id="chat-loading",
185
  type="circle",
186
  children=[
187
+ dbc.Textarea(id="chat-input", placeholder="Enter instructions to refine the document shown above...", className="mb-2", style={'whiteSpace': 'normal', 'wordWrap': 'break-word'}), # Ensure word wrap
188
  dbc.ButtonGroup([
189
  dbc.Button("Send Chat", id="btn-send-chat", color="secondary"),
190
  dbc.Button("Clear Chat", id="btn-clear-chat", color="tertiary")
191
  ], className="mb-3"),
192
+ html.Div(id="chat-output", style={'whiteSpace': 'pre-wrap', 'wordWrap': 'break-word', 'marginTop': '10px', 'border': '1px solid #eee', 'padding': '10px', 'borderRadius': '5px', 'minHeight': '50px'}) # Added wordWrap
193
  ]
194
  )
195
  ]), className="mb-3"
196
  )
197
  ])
198
+ , className="h-100"), # Ensure full height
199
+ width=12, lg=8, # Full width on small, 8/12 (67%) on large
200
+ style={'paddingLeft': '15px'} # Add padding between columns
201
  )
202
  ])
203
+ ], style={'padding': '0 15px'}) # Add padding around the container
204
 
205
 
206
  # --- Helper Functions ---
207
 
208
+ def get_session_id(session_id_value=None):
209
+ """Gets the current session ID or generates a new one."""
210
+ if session_id_value:
211
+ return session_id_value
212
+ # Fallback for initial load or if session ID is missing
213
+ new_id = str(uuid.uuid4())
214
+ logging.info(f"Generated new session ID: {new_id}")
215
+ return new_id
216
+
217
  def process_document(contents, filename):
218
  """Processes uploaded file content (PDF or DOCX) and returns text, or None and error message."""
219
  if contents is None:
 
256
  logging.error(f"Error processing document {filename}: {e}", exc_info=True)
257
  return None, f"Error processing file {filename}: {str(e)}"
258
 
259
+ def get_combined_uploaded_text(session_id):
260
+ """Combines text content of all successfully uploaded files for a session."""
261
+ with data_lock:
262
+ session_files = uploaded_files.get(session_id, {})
263
+ if not session_files:
264
+ return ""
265
+ return "\n\n--- FILE BREAK ---\n\n".join(session_files.values())
266
 
267
+ def generate_ai_document(session_id, doc_type, input_docs, context_docs=None):
268
+ """Generates document using Gemini AI. Updates current_display for the session."""
269
+ global current_display_document # Modifying global state
270
 
271
  if not model:
272
+ logging.error(f"[{session_id}] Gemini AI model not initialized.")
273
  return "Error: AI Model not configured. Please check API Key."
274
  if not input_docs or not any(doc.strip() for doc in input_docs if doc):
275
+ logging.warning(f"[{session_id}] generate_ai_document called for {doc_type} with no valid input documents.")
276
  return f"Error: Missing required input document(s) for {doc_type} generation."
277
 
278
  combined_input = "\n\n---\n\n".join(filter(None, input_docs))
279
  combined_context = "\n\n---\n\n".join(filter(None, context_docs)) if context_docs else ""
280
 
281
+ # Updated Prompt - Ensure it's correctly terminated
282
  prompt = f"""**Objective:** Generate the '{doc_type}' document.
283
  **Your Role:** Act as an expert proposal writer/analyst.
284
  **Core Instructions:**