Sompote commited on
Commit
51d3271
Β·
verified Β·
1 Parent(s): 9570014

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +113 -298
app.py CHANGED
@@ -23,126 +23,90 @@ st.set_page_config(
23
  )
24
 
25
  def setup_llm_provider_gui():
26
- """Setup GUI for LLM provider and API key management"""
27
  st.subheader("πŸ”‘ LLM Provider Setup")
 
28
 
29
- # Get available providers
30
- available_providers = get_available_providers()
 
31
 
32
- if not available_providers:
33
- st.warning("⚠️ No API keys found. Please configure at least one LLM provider:")
34
-
35
- # Show setup for each provider
36
- for provider_id, provider_info in LLM_PROVIDERS.items():
37
- with st.expander(f"πŸ”§ Setup {provider_info['name']}"):
38
- st.markdown(f"**{provider_info['description']}**")
39
-
40
- # API key input
41
- api_key_input = st.text_input(
42
- f"Enter your {provider_info['name']} API Key:",
43
- type="password",
44
- placeholder=get_api_key_placeholder(provider_id),
45
- help=get_provider_help_text(provider_id),
46
- key=f"api_key_{provider_id}"
47
- )
48
-
49
- if st.button(f"πŸ’Ύ Save {provider_info['name']} Key", key=f"save_{provider_id}"):
50
- if api_key_input and validate_api_key_format(provider_id, api_key_input):
51
- try:
52
- save_provider_key_to_env(provider_id, api_key_input)
53
- st.success(f"βœ… {provider_info['name']} API key saved successfully!")
54
- st.info("πŸ”„ Reloading application to activate your new API key...")
55
- st.rerun()
56
- except Exception as e:
57
- st.error(f"❌ Failed to save API key: {str(e)}")
58
- else:
59
- st.error(f"❌ Invalid API key format for {provider_info['name']}")
60
- else:
61
- # Show configured providers
62
- st.success(f"βœ… {len(available_providers)} provider(s) configured")
63
-
64
- for provider_id in available_providers:
65
- provider_info = LLM_PROVIDERS[provider_id]
66
- current_key = get_api_key(provider_id)
67
- masked_key = mask_api_key(current_key)
68
 
69
- # Check if user wants to change this provider's key
70
- if st.session_state.get(f'show_key_input_{provider_id}'):
71
- # Show input form for changing API key
72
- with st.expander(f"πŸ”§ Change {provider_info['name']} API Key", expanded=True):
73
- new_api_key = st.text_input(
74
- f"Enter new {provider_info['name']} API Key:",
75
- type="password",
76
- placeholder=get_api_key_placeholder(provider_id),
77
- key=f"new_key_{provider_id}"
78
- )
79
-
80
- col1, col2 = st.columns(2)
81
- with col1:
82
- if st.button(f"πŸ’Ύ Update {provider_info['name']} Key", key=f"update_{provider_id}"):
83
- if new_api_key and validate_api_key_format(provider_id, new_api_key):
84
- try:
85
- save_provider_key_to_env(provider_id, new_api_key)
86
- st.success(f"βœ… {provider_info['name']} API key updated successfully!")
87
- st.session_state[f'show_key_input_{provider_id}'] = False
88
- st.rerun()
89
- except Exception as e:
90
- st.error(f"❌ Failed to update API key: {str(e)}")
91
- else:
92
- st.error(f"❌ Invalid API key format for {provider_info['name']}")
93
-
94
- with col2:
95
- if st.button("❌ Cancel", key=f"cancel_change_{provider_id}"):
96
- st.session_state[f'show_key_input_{provider_id}'] = False
97
- st.rerun()
98
- else:
99
- # Show normal display with change button
100
- col1, col2 = st.columns([3, 1])
101
- with col1:
102
- st.info(f"**{provider_info['name']}**: {masked_key}")
103
- with col2:
104
- if st.button(f"πŸ”„ Change", key=f"change_{provider_id}"):
105
- st.session_state[f'show_key_input_{provider_id}'] = True
106
- st.rerun()
107
-
108
- # Add new provider section
109
- st.markdown("---")
110
- if st.button("βž• Add Another Provider"):
111
- st.session_state.show_add_provider = True
112
- st.rerun()
113
-
114
- if st.session_state.get('show_add_provider'):
115
- setup_additional_provider()
116
 
117
  def get_current_provider_and_model():
118
- """Get current provider and model from session state or defaults"""
119
- # Check session state first
120
- if 'selected_provider' in st.session_state and 'selected_model' in st.session_state:
121
- return st.session_state.selected_provider, st.session_state.selected_model
122
-
123
- # Get defaults based on available providers
124
- try:
125
- return get_default_provider_and_model()
126
- except:
127
- # If no providers available, return None
128
- return None, None
 
 
 
 
 
 
129
 
130
  def get_api_key_for_current_provider():
131
- """Get API key for currently selected provider"""
132
  provider, _ = get_current_provider_and_model()
133
  if provider:
134
- return get_api_key(provider)
135
- return None
 
136
 
137
- def refresh_provider_status():
138
- """Force refresh of provider status after API key changes"""
139
- from dotenv import load_dotenv
140
- # Reload environment variables to pick up any .env file changes
141
- load_dotenv(override=True)
142
-
143
- # Only clear cached states that are specifically related to stale API operations
144
- # Don't clear active user interface states like show_key_input_
145
- pass
146
 
147
  def get_api_key_placeholder(provider_id):
148
  """Get placeholder text for API key input"""
@@ -186,117 +150,6 @@ def mask_api_key(api_key):
186
  return api_key[:8] + "..." + api_key[-4:]
187
  return "***configured***"
188
 
189
- def save_provider_key_to_env(provider_id, api_key):
190
- """Save provider API key to .env file"""
191
- env_var = LLM_PROVIDERS[provider_id]["api_key_env"]
192
- env_path = ".env"
193
-
194
- try:
195
- # Read existing content
196
- env_content = ""
197
- if os.path.exists(env_path):
198
- with open(env_path, 'r') as f:
199
- env_content = f.read()
200
-
201
- # Update or add the API key
202
- lines = env_content.split('\n')
203
- updated_lines = []
204
- key_found = False
205
-
206
- for line in lines:
207
- # Check for existing key (both active and commented)
208
- if line.startswith(f"{env_var}=") or line.startswith(f"# {env_var}="):
209
- # Replace with active key
210
- updated_lines.append(f"{env_var}={api_key}")
211
- key_found = True
212
- else:
213
- updated_lines.append(line)
214
-
215
- if not key_found:
216
- # Add new key if not found
217
- if updated_lines and updated_lines[-1] != '':
218
- updated_lines.append('')
219
- updated_lines.append(f"{env_var}={api_key}")
220
-
221
- # Write to file
222
- with open(env_path, 'w') as f:
223
- f.write('\n'.join(updated_lines))
224
-
225
- # CRITICAL FIX: Reload environment variables immediately
226
- # Set the environment variable directly in the current process
227
- os.environ[env_var] = api_key
228
-
229
- # Also reload the .env file to pick up any other changes
230
- from dotenv import load_dotenv
231
- load_dotenv(override=True)
232
-
233
- except Exception as e:
234
- raise Exception(f"Failed to save {env_var}: {str(e)}")
235
-
236
- def setup_additional_provider():
237
- """Setup additional provider interface"""
238
- st.subheader("βž• Add Another Provider")
239
-
240
- # Get providers not yet configured
241
- available_providers = get_available_providers()
242
- unconfigured_providers = [p for p in LLM_PROVIDERS.keys() if p not in available_providers]
243
-
244
- if not unconfigured_providers:
245
- st.info("βœ… All providers are already configured!")
246
- st.success("πŸŽ‰ You have access to all available LLM providers")
247
- if st.button("❌ Close"):
248
- st.session_state.show_add_provider = False
249
- st.rerun()
250
- return
251
-
252
- # Provider selection
253
- provider_names = {p: LLM_PROVIDERS[p]["name"] for p in unconfigured_providers}
254
- selected_provider_name = st.selectbox(
255
- "Select Provider to Configure:",
256
- options=list(provider_names.values())
257
- )
258
-
259
- # Find provider ID from name
260
- selected_provider_id = None
261
- for pid, pname in provider_names.items():
262
- if pname == selected_provider_name:
263
- selected_provider_id = pid
264
- break
265
-
266
- if selected_provider_id:
267
- provider_info = LLM_PROVIDERS[selected_provider_id]
268
- st.markdown(f"**{provider_info['description']}**")
269
-
270
- # API key input
271
- api_key_input = st.text_input(
272
- f"Enter your {provider_info['name']} API Key:",
273
- type="password",
274
- placeholder=get_api_key_placeholder(selected_provider_id),
275
- help=get_provider_help_text(selected_provider_id),
276
- key=f"add_api_key_{selected_provider_id}"
277
- )
278
-
279
- col1, col2 = st.columns(2)
280
- with col1:
281
- if st.button(f"πŸ’Ύ Save {provider_info['name']} Key"):
282
- if api_key_input and validate_api_key_format(selected_provider_id, api_key_input):
283
- try:
284
- save_provider_key_to_env(selected_provider_id, api_key_input)
285
- st.success(f"βœ… {provider_info['name']} API key saved successfully!")
286
- st.info("πŸ”„ Reloading application to activate your new API key...")
287
- st.session_state.show_add_provider = False
288
- st.rerun()
289
- except Exception as e:
290
- st.error(f"❌ Failed to save API key: {str(e)}")
291
- else:
292
- st.error(f"❌ Invalid API key format for {provider_info['name']}")
293
-
294
- with col2:
295
- if st.button("❌ Cancel"):
296
- st.session_state.show_add_provider = False
297
- st.rerun()
298
-
299
-
300
  def initialize_crewai_system():
301
  """Initialize CrewAI system with current settings"""
302
  provider, model = get_current_provider_and_model()
@@ -449,45 +302,25 @@ def main():
449
  st.title("πŸ—οΈ Soil Boring Log Analyzer")
450
  st.markdown("Upload soil boring logs (PDF/Image) to automatically extract and analyze soil layers using AI")
451
 
452
- # Force refresh provider status on each run to catch any newly saved API keys
453
- refresh_provider_status()
454
-
455
  # Show system status
456
- available_providers = get_available_providers()
457
- if available_providers:
458
- provider_names = [LLM_PROVIDERS[p]["name"] for p in available_providers]
459
- st.success(f"βœ… **Ready to use** - Configured providers: {', '.join(provider_names)}")
 
460
  else:
461
- st.info("πŸ”§ **Setup Required** - Please configure at least one LLM provider below to start analyzing soil boring logs")
462
-
463
- # LLM Provider Management
464
- available_providers = get_available_providers()
465
-
466
- if not available_providers:
467
- st.error("⚠️ At least one LLM provider API key is required to use this application")
468
- setup_llm_provider_gui()
469
- return
470
 
471
- # Show provider management in sidebar
472
  with st.sidebar:
 
 
 
 
 
 
 
473
  st.markdown("---")
474
- setup_llm_provider_gui()
475
-
476
- # Initialize components lazily
477
- if 'document_processor' not in st.session_state:
478
- st.session_state.document_processor = DocumentProcessor()
479
-
480
- if 'agent' not in st.session_state:
481
- st.session_state.agent = SoilAnalysisAgent()
482
-
483
- if 'visualizer' not in st.session_state:
484
- st.session_state.visualizer = SoilProfileVisualizer()
485
-
486
- if 'analysis_results' not in st.session_state:
487
- st.session_state.analysis_results = None
488
-
489
- # Sidebar
490
- with st.sidebar:
491
  st.header("Upload Document")
492
  uploaded_file = st.file_uploader(
493
  "Choose a soil boring log file",
@@ -506,42 +339,9 @@ def main():
506
  help="CrewAI uses two specialized agents with quality control"
507
  )
508
 
509
- # Provider and Model selection
510
- st.subheader("πŸ€– LLM Provider & Model Selection")
511
-
512
- # Get available providers
513
- available_providers = get_available_providers()
514
-
515
- if not available_providers:
516
- st.error("⚠️ No LLM providers configured. Please set up at least one provider in the sidebar.")
517
- return
518
-
519
- # Provider selection
520
- provider_names = {p: LLM_PROVIDERS[p]["name"] for p in available_providers}
521
- current_provider, current_model = get_current_provider_and_model()
522
-
523
- # Default provider selection
524
- default_provider_name = None
525
- if current_provider and current_provider in provider_names:
526
- default_provider_name = provider_names[current_provider]
527
- elif provider_names:
528
- default_provider_name = list(provider_names.values())[0]
529
-
530
- selected_provider_name = st.selectbox(
531
- "Select LLM Provider:",
532
- options=list(provider_names.values()),
533
- index=list(provider_names.values()).index(default_provider_name) if default_provider_name else 0,
534
- help="Choose your preferred LLM provider"
535
- )
536
-
537
- # Find provider ID from name
538
- selected_provider = None
539
- for pid, pname in provider_names.items():
540
- if pname == selected_provider_name:
541
- selected_provider = pid
542
- break
543
-
544
  # Model selection for selected provider
 
 
545
  if selected_provider:
546
  available_models = get_models_for_provider(selected_provider)
547
 
@@ -557,6 +357,7 @@ def main():
557
  model_options[label] = model_id
558
 
559
  # Default model selection
 
560
  default_model_label = None
561
  if current_model and current_model in available_models:
562
  for label, model_id in model_options.items():
@@ -568,7 +369,7 @@ def main():
568
  default_model_label = list(model_options.keys())[0]
569
 
570
  selected_label = st.selectbox(
571
- f"Select Model for {selected_provider_name}:",
572
  options=list(model_options.keys()),
573
  index=list(model_options.keys()).index(default_model_label) if default_model_label else 0,
574
  help="⭐ = Recommended | πŸ“ = Text-only (no image support)"
@@ -576,6 +377,9 @@ def main():
576
 
577
  selected_model = model_options[selected_label]
578
 
 
 
 
579
  # Show model info
580
  if selected_model in AVAILABLE_MODELS:
581
  model_info = AVAILABLE_MODELS[selected_model]
@@ -591,14 +395,7 @@ def main():
591
  else:
592
  st.warning("πŸ“ This model supports text-only analysis (images will be ignored)")
593
  else:
594
- st.error(f"No models available for {selected_provider_name}")
595
- selected_model = None
596
- else:
597
- selected_model = None
598
-
599
- # Store selections in session state
600
- st.session_state.selected_provider = selected_provider
601
- st.session_state.selected_model = selected_model
602
 
603
  if st.button("πŸ”„ Reset Analysis"):
604
  st.session_state.analysis_results = None
@@ -678,6 +475,24 @@ Notes: All strength values from SPT testing. Su calculated using Su=5*N for clay
678
  except Exception as e:
679
  st.error(f"❌ Sample analysis error: {str(e)}")
680
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
681
  # Main content
682
  if uploaded_file is not None:
683
  # Process document
 
23
  )
24
 
25
  def setup_llm_provider_gui():
26
+ """Setup GUI for temporary LLM provider and API key input"""
27
  st.subheader("πŸ”‘ LLM Provider Setup")
28
+ st.info("πŸ’‘ API keys are used temporarily for this session only and are not saved permanently.")
29
 
30
+ # Provider selection
31
+ provider_options = {provider_info['name']: provider_id
32
+ for provider_id, provider_info in LLM_PROVIDERS.items()}
33
 
34
+ selected_provider_name = st.selectbox(
35
+ "Select LLM Provider:",
36
+ options=list(provider_options.keys()),
37
+ help="Choose your preferred LLM provider"
38
+ )
39
+
40
+ selected_provider = provider_options[selected_provider_name]
41
+ provider_info = LLM_PROVIDERS[selected_provider]
42
+
43
+ st.markdown(f"**{provider_info['description']}**")
44
+
45
+ # API key input
46
+ session_key = f"temp_api_key_{selected_provider}"
47
+ current_key = st.session_state.get(session_key, "")
48
+
49
+ api_key_input = st.text_input(
50
+ f"Enter your {provider_info['name']} API Key:",
51
+ value=current_key,
52
+ type="password",
53
+ placeholder=get_api_key_placeholder(selected_provider),
54
+ help=f"{get_provider_help_text(selected_provider)} (Temporary use only - not saved)",
55
+ key=f"api_key_input_{selected_provider}"
56
+ )
57
+
58
+ # Validate and store in session
59
+ if api_key_input:
60
+ if validate_api_key_format(selected_provider, api_key_input):
61
+ st.session_state[session_key] = api_key_input
62
+ st.session_state['selected_provider'] = selected_provider
63
+ st.success(f"βœ… {provider_info['name']} API key ready for use")
 
 
 
 
 
 
64
 
65
+ # Show masked key
66
+ masked_key = mask_api_key(api_key_input)
67
+ st.info(f"πŸ” Current key: {masked_key}")
68
+ else:
69
+ st.error(f"❌ Invalid API key format for {provider_info['name']}")
70
+ if session_key in st.session_state:
71
+ del st.session_state[session_key]
72
+ else:
73
+ st.warning(f"⚠️ Please enter your {provider_info['name']} API key to continue")
74
+ if session_key in st.session_state:
75
+ del st.session_state[session_key]
76
+
77
+ return selected_provider, api_key_input
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
 
79
  def get_current_provider_and_model():
80
+ """Get current provider and model from session state"""
81
+ provider = st.session_state.get('selected_provider')
82
+ model = st.session_state.get('selected_model')
83
+
84
+ # If no provider set, try to get first available one
85
+ if not provider:
86
+ available_providers = list(LLM_PROVIDERS.keys())
87
+ if available_providers:
88
+ provider = available_providers[0]
89
+
90
+ # If no model set, try to get first available model for provider
91
+ if not model and provider:
92
+ available_models = get_models_for_provider(provider)
93
+ if available_models:
94
+ model = list(available_models.keys())[0]
95
+
96
+ return provider, model
97
 
98
  def get_api_key_for_current_provider():
99
+ """Get API key for currently selected provider from session state"""
100
  provider, _ = get_current_provider_and_model()
101
  if provider:
102
+ session_key = f"temp_api_key_{provider}"
103
+ return st.session_state.get(session_key, "")
104
+ return ""
105
 
106
+ def is_provider_configured():
107
+ """Check if current provider is configured with API key"""
108
+ api_key = get_api_key_for_current_provider()
109
+ return bool(api_key and api_key.strip())
 
 
 
 
 
110
 
111
  def get_api_key_placeholder(provider_id):
112
  """Get placeholder text for API key input"""
 
150
  return api_key[:8] + "..." + api_key[-4:]
151
  return "***configured***"
152
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
153
  def initialize_crewai_system():
154
  """Initialize CrewAI system with current settings"""
155
  provider, model = get_current_provider_and_model()
 
302
  st.title("πŸ—οΈ Soil Boring Log Analyzer")
303
  st.markdown("Upload soil boring logs (PDF/Image) to automatically extract and analyze soil layers using AI")
304
 
 
 
 
305
  # Show system status
306
+ if is_provider_configured():
307
+ provider, _ = get_current_provider_and_model()
308
+ if provider:
309
+ provider_name = LLM_PROVIDERS[provider]["name"]
310
+ st.success(f"βœ… **Ready to use** - Using {provider_name} (API key provided)")
311
  else:
312
+ st.info("πŸ”§ **Setup Required** - Please enter your API key in the sidebar to start analyzing soil boring logs")
 
 
 
 
 
 
 
 
313
 
314
+ # LLM Provider Management in Sidebar
315
  with st.sidebar:
316
+ selected_provider, api_key = setup_llm_provider_gui()
317
+
318
+ # Only show rest of sidebar if API key is provided
319
+ if not is_provider_configured():
320
+ st.warning("⚠️ Please enter a valid API key above to continue")
321
+ return
322
+
323
  st.markdown("---")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
324
  st.header("Upload Document")
325
  uploaded_file = st.file_uploader(
326
  "Choose a soil boring log file",
 
339
  help="CrewAI uses two specialized agents with quality control"
340
  )
341
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  # Model selection for selected provider
343
+ st.subheader("πŸ€– Model Selection")
344
+
345
  if selected_provider:
346
  available_models = get_models_for_provider(selected_provider)
347
 
 
357
  model_options[label] = model_id
358
 
359
  # Default model selection
360
+ current_model = st.session_state.get('selected_model')
361
  default_model_label = None
362
  if current_model and current_model in available_models:
363
  for label, model_id in model_options.items():
 
369
  default_model_label = list(model_options.keys())[0]
370
 
371
  selected_label = st.selectbox(
372
+ f"Select Model:",
373
  options=list(model_options.keys()),
374
  index=list(model_options.keys()).index(default_model_label) if default_model_label else 0,
375
  help="⭐ = Recommended | πŸ“ = Text-only (no image support)"
 
377
 
378
  selected_model = model_options[selected_label]
379
 
380
+ # Store model selection in session state
381
+ st.session_state.selected_model = selected_model
382
+
383
  # Show model info
384
  if selected_model in AVAILABLE_MODELS:
385
  model_info = AVAILABLE_MODELS[selected_model]
 
395
  else:
396
  st.warning("πŸ“ This model supports text-only analysis (images will be ignored)")
397
  else:
398
+ st.error(f"No models available for {LLM_PROVIDERS[selected_provider]['name']}")
 
 
 
 
 
 
 
399
 
400
  if st.button("πŸ”„ Reset Analysis"):
401
  st.session_state.analysis_results = None
 
475
  except Exception as e:
476
  st.error(f"❌ Sample analysis error: {str(e)}")
477
 
478
+ # Check if provider is configured before proceeding
479
+ if not is_provider_configured():
480
+ st.warning("⚠️ Please configure an API key in the sidebar to start using the application")
481
+ return
482
+
483
+ # Initialize components lazily
484
+ if 'document_processor' not in st.session_state:
485
+ st.session_state.document_processor = DocumentProcessor()
486
+
487
+ if 'agent' not in st.session_state:
488
+ st.session_state.agent = SoilAnalysisAgent()
489
+
490
+ if 'visualizer' not in st.session_state:
491
+ st.session_state.visualizer = SoilProfileVisualizer()
492
+
493
+ if 'analysis_results' not in st.session_state:
494
+ st.session_state.analysis_results = None
495
+
496
  # Main content
497
  if uploaded_file is not None:
498
  # Process document