mgbam commited on
Commit
4369489
Β·
verified Β·
1 Parent(s): ad3750e

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +77 -72
app.py CHANGED
@@ -6,13 +6,14 @@ import json
6
  import os # Still needed for API key potentially, but not model names
7
  from pathlib import Path
8
  import time
 
 
9
 
10
  # --- Configuration ---
11
- # Model names are now discovered dynamically. Remove hardcoded names.
12
- MAX_PROMPT_TOKENS_ESTIMATE = 800000 # Keep this estimate
13
  RESULTS_PAGE_SIZE = 25
14
 
15
- AVAILABLE_ANALYSES = { # Keep analyses config
16
  "generate_docs": "Generate Missing Docstrings/Comments",
17
  "find_bugs": "Identify Potential Bugs & Anti-patterns",
18
  "check_style": "Check Style Guide Compliance (General)",
@@ -20,12 +21,11 @@ AVAILABLE_ANALYSES = { # Keep analyses config
20
  "suggest_refactoring": "Suggest Refactoring Opportunities",
21
  }
22
  CODE_EXTENSIONS = {
23
- '.py', '.js', '.java', '.c', '.cpp', '.h', '.cs', '.go', '.rb',
24
  '.php', '.swift', '.kt', '.ts', '.html', '.css', '.scss', '.sql'
25
- } # Keep extensions
26
 
27
  # --- Session State Initialization ---
28
- # (Keep most session state, add one for the selected model)
29
  if 'mock_api_call' not in st.session_state:
30
  st.session_state.mock_api_call = False
31
  if 'analysis_results' not in st.session_state:
@@ -35,79 +35,73 @@ if 'error_message' not in st.session_state:
35
  if 'analysis_requested' not in st.session_state:
36
  st.session_state.analysis_requested = False
37
  if 'selected_model_name' not in st.session_state:
38
- st.session_state.selected_model_name = None # Will hold the "models/..." name
39
  if 'available_models_dict' not in st.session_state:
40
- st.session_state.available_models_dict = {} # Store display_name -> name mapping
 
 
 
 
41
 
42
  # --- Gemini API Setup & Model Discovery ---
43
  model = None # Global variable for the initialized model instance
44
 
45
- # --- NEW: Function to list available models ---
46
- @st.cache_data(ttl=3600) # Cache model list for an hour
47
  def get_available_models():
48
  """Lists models supporting 'generateContent' using the API key."""
49
  model_dict = {}
50
  try:
51
  if 'GEMINI_API_KEY' not in st.secrets:
52
- # Don't stop here, let the main part handle it, but return empty
53
  print("API key not found in secrets during model listing attempt.")
54
  return {}
55
- # Configure API key temporarily just for listing
56
  genai.configure(api_key=st.secrets["GEMINI_API_KEY"])
57
  print("Listing available models via API...")
58
  for m in genai.list_models():
59
- # Check if the model supports the 'generateContent' method
60
  if 'generateContent' in m.supported_generation_methods:
61
- # Store mapping: user-friendly name -> internal name
62
  model_dict[m.display_name] = m.name
63
  print(f"Found {len(model_dict)} compatible models.")
64
  return model_dict
65
  except Exception as e:
66
  st.error(f"🚨 Error listing available models: {e}")
67
- return {} # Return empty on error
68
 
69
  def initialize_gemini_model():
70
  """Initializes the Gemini model based on the selected name."""
71
  global model
72
  selected_name = st.session_state.get('selected_model_name')
73
-
74
  if selected_name and model is None and not st.session_state.mock_api_call:
75
  try:
76
  if 'GEMINI_API_KEY' not in st.secrets:
77
  st.error("🚨 Gemini API Key not found. Add it to `.streamlit/secrets.toml`.")
78
- st.stop() # Stop if key missing for initialization
79
- # Configure API key (might be redundant if list_models worked, but safe)
80
  genai.configure(api_key=st.secrets["GEMINI_API_KEY"])
81
  print(f"Initializing Gemini Model: {selected_name}")
82
- # Use the selected model name from session state
83
  model = genai.GenerativeModel(model_name=selected_name)
84
  print(f"Gemini Model Initialized ({selected_name}).")
85
  return True
86
  except Exception as e:
87
  st.error(f"🚨 Error initializing selected Gemini model '{selected_name}': {e}")
88
- st.session_state.selected_model_name = None # Reset selection on error
89
  st.stop()
90
  return False
91
  elif st.session_state.mock_api_call:
92
- return True # No init needed for mock mode
93
  elif model is not None and model.model_name == selected_name:
94
- return True # Already initialized with the correct model
95
  elif model is not None and model.model_name != selected_name:
96
  print("Model changed. Re-initializing...")
97
- model = None # Reset model instance
98
- return initialize_gemini_model() # Recurse to re-initialize with new name
99
  elif not selected_name and not st.session_state.mock_api_call:
100
- # This case happens if no model is selected yet
101
- return False # Cannot initialize without a selection
102
- return False # Default case
103
 
104
  # --- Helper Functions ---
105
- # Updated estimate_token_count to support integers and strings
106
  def estimate_token_count(text):
107
  """
108
  Estimates the token count.
109
- If a string is provided, it calculates based on its length.
110
- If an integer is provided (e.g., total character count), it uses that directly.
111
  """
112
  if isinstance(text, int):
113
  return text // 3
@@ -117,7 +111,7 @@ def estimate_token_count(text):
117
  def process_zip_file_cached(file_id, file_size, file_content_bytes):
118
  """
119
  Processes a ZIP file and extracts code files.
120
- Returns a tuple of (code_files dict, total_chars, file_count, ignored_files list).
121
  """
122
  code_files = {}
123
  total_chars = 0
@@ -166,16 +160,13 @@ def process_zip_file_cached(file_id, file_size, file_content_bytes):
166
  st.error(f"🚨 ZIP Error: {e}")
167
  return None, 0, 0, []
168
  if file_count == 0:
169
- if not ignored_files:
170
- st.warning("No code files found.")
171
- else:
172
- st.warning("No code files found; some skipped.")
173
  return code_files, total_chars, file_count, ignored_files
174
 
175
  def construct_analysis_prompt(code_files_dict, requested_analyses):
176
  """
177
- Constructs the prompt for analysis by including code files and JSON structure for expected output.
178
- Returns the full prompt and a list of included files.
179
  """
180
  prompt_parts = ["Analyze the following codebase...\n\n"]
181
  current_token_estimate = estimate_token_count(prompt_parts[0])
@@ -227,12 +218,11 @@ def construct_analysis_prompt(code_files_dict, requested_analyses):
227
  def call_gemini_api(prompt):
228
  """
229
  Calls the Gemini API using the provided prompt.
230
- Returns the parsed JSON insights or an error message.
231
  """
232
  if not prompt:
233
  return None, "Prompt generation failed."
234
 
235
- # MOCK MODE
236
  if st.session_state.mock_api_call:
237
  st.info(" MOCK MODE: Simulating API call...")
238
  time.sleep(1)
@@ -245,12 +235,11 @@ def call_gemini_api(prompt):
245
  })
246
  st.success("Mock response generated.")
247
  return json.loads(mock_json_response), None
248
- # REAL API CALL
249
  else:
250
  if not initialize_gemini_model():
251
  return None, "Gemini Model Initialization Failed."
252
  if model is None:
253
- return None, "Gemini model not selected or available." # Added check
254
  try:
255
  api_status = st.empty()
256
  api_status.info(f"πŸ“‘ Sending request to {model.model_name} (Est. prompt tokens: {estimate_token_count(prompt):,})... Please wait.")
@@ -270,7 +259,6 @@ def call_gemini_api(prompt):
270
  api_status.empty()
271
  try:
272
  json_response_text = response.text.strip()
273
- # Remove markdown code fences if present
274
  if json_response_text.startswith("```json"):
275
  json_response_text = json_response_text[7:]
276
  if json_response_text.startswith("```"):
@@ -320,7 +308,7 @@ def call_gemini_api(prompt):
320
 
321
  def display_results(results_json, requested_analyses):
322
  """
323
- Displays the analysis results with pagination and allows JSON download.
324
  """
325
  st.header("πŸ“Š Analysis Report")
326
  if not isinstance(results_json, dict):
@@ -407,8 +395,21 @@ def display_results(results_json, requested_analyses):
407
  st.set_page_config(page_title="Codebase Audit Assistant", layout="wide")
408
  st.title("πŸ€– Codebase Audit & Documentation Assistant")
409
 
410
- # --- Sidebar ---
411
  with st.sidebar:
 
 
 
 
 
 
 
 
 
 
 
 
 
412
  st.header("βš™οΈ Analysis Controls")
413
  st.session_state.mock_api_call = st.toggle(
414
  "πŸ§ͺ Enable Mock API Mode",
@@ -418,27 +419,20 @@ with st.sidebar:
418
 
419
  st.divider()
420
  st.header("β™Š Select Model")
421
- # --- NEW: Dynamic Model Selection ---
422
  if not st.session_state.mock_api_call:
423
- # Get available models (uses cache)
424
  st.session_state.available_models_dict = get_available_models()
425
  model_display_names = list(st.session_state.available_models_dict.keys())
426
-
427
  if model_display_names:
428
- # Try to find the index of the previously selected model
429
  current_model_display_name = None
430
  if st.session_state.selected_model_name:
431
- # Find display name matching the stored internal name
432
  for disp_name, internal_name in st.session_state.available_models_dict.items():
433
  if internal_name == st.session_state.selected_model_name:
434
  current_model_display_name = disp_name
435
  break
436
-
437
  try:
438
  selected_index = model_display_names.index(current_model_display_name) if current_model_display_name in model_display_names else 0
439
  except ValueError:
440
- selected_index = 0 # Default to first if previous selection not found
441
-
442
  selected_display_name = st.selectbox(
443
  "Choose Gemini model:",
444
  options=model_display_names,
@@ -446,20 +440,18 @@ with st.sidebar:
446
  key="model_selector",
447
  help="Select the Gemini model to use for analysis."
448
  )
449
- # Update session state with the internal name based on selection
450
  st.session_state.selected_model_name = st.session_state.available_models_dict.get(selected_display_name)
451
  st.info(f"Using REAL Gemini API ({st.session_state.selected_model_name})")
452
  elif 'GEMINI_API_KEY' in st.secrets:
453
  st.warning("No compatible models found or error listing models. Check API Key permissions.")
454
- st.session_state.selected_model_name = None # Ensure no model selected
455
  else:
456
  st.warning("Add GEMINI_API_KEY to secrets to list models.")
457
  st.session_state.selected_model_name = None
458
- else: # Mock mode is active
459
  st.info("Mock API Mode ACTIVE")
460
- st.session_state.selected_model_name = "mock_model" # Use a placeholder name for mock mode
461
- # --- End Dynamic Model Selection ---
462
-
463
  st.divider()
464
  st.header("πŸ”Ž Select Analyses")
465
  selected_analyses = [
@@ -481,7 +473,7 @@ with st.sidebar:
481
  st.divider()
482
  st.warning("⚠️ **Privacy:** Code sent to Google API if Mock Mode is OFF.")
483
 
484
- # Update title dynamically based on selected model
485
  if st.session_state.selected_model_name and not st.session_state.mock_api_call:
486
  st.markdown(f"Upload codebase (`.zip`) for analysis via **{st.session_state.selected_model_name}**.")
487
  elif st.session_state.mock_api_call:
@@ -511,12 +503,13 @@ if uploaded_file:
511
  file_id, uploaded_file.size, uploaded_file_bytes
512
  )
513
  if code_files is not None:
 
 
 
514
  st.info(f"Found **{file_count}** code files ({total_chars:,} chars). Est. tokens: ~{estimate_token_count(total_chars):,}")
515
  if ignored_files:
516
  with st.expander(f"View {len(ignored_files)} Skipped/Ignored Files"):
517
  st.code("\n".join(ignored_files), language='text')
518
-
519
- # Disable button if no model selected (and not in mock mode)
520
  model_ready = bool(st.session_state.selected_model_name) or st.session_state.mock_api_call
521
  analyze_button_disabled = (not selected_analyses or file_count == 0 or not model_ready)
522
  analyze_button_label = "Analyze Codebase"
@@ -525,11 +518,7 @@ if uploaded_file:
525
  elif analyze_button_disabled:
526
  analyze_button_label = "Select Analyses or Upload Valid Code"
527
 
528
- if analysis_button_placeholder.button(
529
- analyze_button_label,
530
- type="primary",
531
- disabled=analyze_button_disabled
532
- ):
533
  st.session_state.analysis_requested = True
534
  st.session_state.analysis_results = None
535
  st.session_state.error_message = None
@@ -541,11 +530,7 @@ if uploaded_file:
541
  st.warning("Please select a Gemini model from the sidebar.")
542
  else:
543
  with results_placeholder:
544
- spinner_model_name = (
545
- st.session_state.selected_model_name
546
- if not st.session_state.mock_api_call
547
- else "Mock Mode"
548
- )
549
  spinner_msg = f"πŸš€ Preparing prompt & contacting AI ({spinner_model_name})... Please wait."
550
  with st.spinner(spinner_msg):
551
  analysis_prompt, included_files_in_prompt = construct_analysis_prompt(code_files, selected_analyses)
@@ -559,7 +544,7 @@ if uploaded_file:
559
  st.session_state.error_message = "Failed to generate analysis prompt."
560
  st.rerun()
561
 
562
- # Display results (Keep the same logic)
563
  if st.session_state.analysis_requested:
564
  with results_placeholder:
565
  st.divider()
@@ -575,5 +560,25 @@ if st.session_state.analysis_requested:
575
  elif not uploaded_file:
576
  results_placeholder.info("Upload a ZIP file to begin.")
577
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
578
  results_placeholder.divider()
579
  results_placeholder.markdown("_Assistant powered by Google Gemini._")
 
6
  import os # Still needed for API key potentially, but not model names
7
  from pathlib import Path
8
  import time
9
+ import plotly.express as px
10
+ import pandas as pd
11
 
12
  # --- Configuration ---
13
+ MAX_PROMPT_TOKENS_ESTIMATE = 800000 # Token estimate limit
 
14
  RESULTS_PAGE_SIZE = 25
15
 
16
+ AVAILABLE_ANALYSES = {
17
  "generate_docs": "Generate Missing Docstrings/Comments",
18
  "find_bugs": "Identify Potential Bugs & Anti-patterns",
19
  "check_style": "Check Style Guide Compliance (General)",
 
21
  "suggest_refactoring": "Suggest Refactoring Opportunities",
22
  }
23
  CODE_EXTENSIONS = {
24
+ '.py', '.js', '.java', '.c', '.cpp', '.h', '.cs', '.go', '.rb',
25
  '.php', '.swift', '.kt', '.ts', '.html', '.css', '.scss', '.sql'
26
+ }
27
 
28
  # --- Session State Initialization ---
 
29
  if 'mock_api_call' not in st.session_state:
30
  st.session_state.mock_api_call = False
31
  if 'analysis_results' not in st.session_state:
 
35
  if 'analysis_requested' not in st.session_state:
36
  st.session_state.analysis_requested = False
37
  if 'selected_model_name' not in st.session_state:
38
+ st.session_state.selected_model_name = None # Internal model name
39
  if 'available_models_dict' not in st.session_state:
40
+ st.session_state.available_models_dict = {} # Mapping: display -> internal
41
+ if 'file_count' not in st.session_state:
42
+ st.session_state.file_count = 0
43
+ if 'total_chars' not in st.session_state:
44
+ st.session_state.total_chars = 0
45
 
46
  # --- Gemini API Setup & Model Discovery ---
47
  model = None # Global variable for the initialized model instance
48
 
49
+ @st.cache_data(ttl=3600)
 
50
  def get_available_models():
51
  """Lists models supporting 'generateContent' using the API key."""
52
  model_dict = {}
53
  try:
54
  if 'GEMINI_API_KEY' not in st.secrets:
 
55
  print("API key not found in secrets during model listing attempt.")
56
  return {}
 
57
  genai.configure(api_key=st.secrets["GEMINI_API_KEY"])
58
  print("Listing available models via API...")
59
  for m in genai.list_models():
 
60
  if 'generateContent' in m.supported_generation_methods:
 
61
  model_dict[m.display_name] = m.name
62
  print(f"Found {len(model_dict)} compatible models.")
63
  return model_dict
64
  except Exception as e:
65
  st.error(f"🚨 Error listing available models: {e}")
66
+ return {}
67
 
68
  def initialize_gemini_model():
69
  """Initializes the Gemini model based on the selected name."""
70
  global model
71
  selected_name = st.session_state.get('selected_model_name')
 
72
  if selected_name and model is None and not st.session_state.mock_api_call:
73
  try:
74
  if 'GEMINI_API_KEY' not in st.secrets:
75
  st.error("🚨 Gemini API Key not found. Add it to `.streamlit/secrets.toml`.")
76
+ st.stop()
 
77
  genai.configure(api_key=st.secrets["GEMINI_API_KEY"])
78
  print(f"Initializing Gemini Model: {selected_name}")
 
79
  model = genai.GenerativeModel(model_name=selected_name)
80
  print(f"Gemini Model Initialized ({selected_name}).")
81
  return True
82
  except Exception as e:
83
  st.error(f"🚨 Error initializing selected Gemini model '{selected_name}': {e}")
84
+ st.session_state.selected_model_name = None
85
  st.stop()
86
  return False
87
  elif st.session_state.mock_api_call:
88
+ return True
89
  elif model is not None and model.model_name == selected_name:
90
+ return True
91
  elif model is not None and model.model_name != selected_name:
92
  print("Model changed. Re-initializing...")
93
+ model = None
94
+ return initialize_gemini_model()
95
  elif not selected_name and not st.session_state.mock_api_call:
96
+ return False
97
+ return False
 
98
 
99
  # --- Helper Functions ---
 
100
  def estimate_token_count(text):
101
  """
102
  Estimates the token count.
103
+ If a string is provided, calculates based on length.
104
+ If an integer is provided (e.g., total character count), uses that directly.
105
  """
106
  if isinstance(text, int):
107
  return text // 3
 
111
  def process_zip_file_cached(file_id, file_size, file_content_bytes):
112
  """
113
  Processes a ZIP file and extracts code files.
114
+ Returns (code_files dict, total_chars, file_count, ignored_files list).
115
  """
116
  code_files = {}
117
  total_chars = 0
 
160
  st.error(f"🚨 ZIP Error: {e}")
161
  return None, 0, 0, []
162
  if file_count == 0:
163
+ st.warning("No code files found." if not ignored_files else "No code files found; some skipped.")
 
 
 
164
  return code_files, total_chars, file_count, ignored_files
165
 
166
  def construct_analysis_prompt(code_files_dict, requested_analyses):
167
  """
168
+ Constructs the prompt for analysis by including code files and JSON structure.
169
+ Returns the full prompt and list of included files.
170
  """
171
  prompt_parts = ["Analyze the following codebase...\n\n"]
172
  current_token_estimate = estimate_token_count(prompt_parts[0])
 
218
  def call_gemini_api(prompt):
219
  """
220
  Calls the Gemini API using the provided prompt.
221
+ Returns parsed JSON insights or an error message.
222
  """
223
  if not prompt:
224
  return None, "Prompt generation failed."
225
 
 
226
  if st.session_state.mock_api_call:
227
  st.info(" MOCK MODE: Simulating API call...")
228
  time.sleep(1)
 
235
  })
236
  st.success("Mock response generated.")
237
  return json.loads(mock_json_response), None
 
238
  else:
239
  if not initialize_gemini_model():
240
  return None, "Gemini Model Initialization Failed."
241
  if model is None:
242
+ return None, "Gemini model not selected or available."
243
  try:
244
  api_status = st.empty()
245
  api_status.info(f"πŸ“‘ Sending request to {model.model_name} (Est. prompt tokens: {estimate_token_count(prompt):,})... Please wait.")
 
259
  api_status.empty()
260
  try:
261
  json_response_text = response.text.strip()
 
262
  if json_response_text.startswith("```json"):
263
  json_response_text = json_response_text[7:]
264
  if json_response_text.startswith("```"):
 
308
 
309
  def display_results(results_json, requested_analyses):
310
  """
311
+ Displays analysis results with pagination and a downloadable JSON report.
312
  """
313
  st.header("πŸ“Š Analysis Report")
314
  if not isinstance(results_json, dict):
 
395
  st.set_page_config(page_title="Codebase Audit Assistant", layout="wide")
396
  st.title("πŸ€– Codebase Audit & Documentation Assistant")
397
 
398
+ # --- Sidebar Enhancements ---
399
  with st.sidebar:
400
+ # Dark Mode Toggle
401
+ dark_mode = st.checkbox("Enable Dark Mode", value=False)
402
+ if dark_mode:
403
+ st.markdown(
404
+ """
405
+ <style>
406
+ .reportview-container { background-color: #2E2E2E; color: white; }
407
+ .sidebar .sidebar-content { background-color: #1A1A1A; }
408
+ </style>
409
+ """,
410
+ unsafe_allow_html=True
411
+ )
412
+
413
  st.header("βš™οΈ Analysis Controls")
414
  st.session_state.mock_api_call = st.toggle(
415
  "πŸ§ͺ Enable Mock API Mode",
 
419
 
420
  st.divider()
421
  st.header("β™Š Select Model")
 
422
  if not st.session_state.mock_api_call:
 
423
  st.session_state.available_models_dict = get_available_models()
424
  model_display_names = list(st.session_state.available_models_dict.keys())
 
425
  if model_display_names:
 
426
  current_model_display_name = None
427
  if st.session_state.selected_model_name:
 
428
  for disp_name, internal_name in st.session_state.available_models_dict.items():
429
  if internal_name == st.session_state.selected_model_name:
430
  current_model_display_name = disp_name
431
  break
 
432
  try:
433
  selected_index = model_display_names.index(current_model_display_name) if current_model_display_name in model_display_names else 0
434
  except ValueError:
435
+ selected_index = 0
 
436
  selected_display_name = st.selectbox(
437
  "Choose Gemini model:",
438
  options=model_display_names,
 
440
  key="model_selector",
441
  help="Select the Gemini model to use for analysis."
442
  )
 
443
  st.session_state.selected_model_name = st.session_state.available_models_dict.get(selected_display_name)
444
  st.info(f"Using REAL Gemini API ({st.session_state.selected_model_name})")
445
  elif 'GEMINI_API_KEY' in st.secrets:
446
  st.warning("No compatible models found or error listing models. Check API Key permissions.")
447
+ st.session_state.selected_model_name = None
448
  else:
449
  st.warning("Add GEMINI_API_KEY to secrets to list models.")
450
  st.session_state.selected_model_name = None
451
+ else:
452
  st.info("Mock API Mode ACTIVE")
453
+ st.session_state.selected_model_name = "mock_model"
454
+
 
455
  st.divider()
456
  st.header("πŸ”Ž Select Analyses")
457
  selected_analyses = [
 
473
  st.divider()
474
  st.warning("⚠️ **Privacy:** Code sent to Google API if Mock Mode is OFF.")
475
 
476
+ # Update title based on selected model
477
  if st.session_state.selected_model_name and not st.session_state.mock_api_call:
478
  st.markdown(f"Upload codebase (`.zip`) for analysis via **{st.session_state.selected_model_name}**.")
479
  elif st.session_state.mock_api_call:
 
503
  file_id, uploaded_file.size, uploaded_file_bytes
504
  )
505
  if code_files is not None:
506
+ # Save these metrics to session state for dashboard use
507
+ st.session_state.file_count = file_count
508
+ st.session_state.total_chars = total_chars
509
  st.info(f"Found **{file_count}** code files ({total_chars:,} chars). Est. tokens: ~{estimate_token_count(total_chars):,}")
510
  if ignored_files:
511
  with st.expander(f"View {len(ignored_files)} Skipped/Ignored Files"):
512
  st.code("\n".join(ignored_files), language='text')
 
 
513
  model_ready = bool(st.session_state.selected_model_name) or st.session_state.mock_api_call
514
  analyze_button_disabled = (not selected_analyses or file_count == 0 or not model_ready)
515
  analyze_button_label = "Analyze Codebase"
 
518
  elif analyze_button_disabled:
519
  analyze_button_label = "Select Analyses or Upload Valid Code"
520
 
521
+ if analysis_button_placeholder.button(analyze_button_label, type="primary", disabled=analyze_button_disabled):
 
 
 
 
522
  st.session_state.analysis_requested = True
523
  st.session_state.analysis_results = None
524
  st.session_state.error_message = None
 
530
  st.warning("Please select a Gemini model from the sidebar.")
531
  else:
532
  with results_placeholder:
533
+ spinner_model_name = st.session_state.selected_model_name if not st.session_state.mock_api_call else "Mock Mode"
 
 
 
 
534
  spinner_msg = f"πŸš€ Preparing prompt & contacting AI ({spinner_model_name})... Please wait."
535
  with st.spinner(spinner_msg):
536
  analysis_prompt, included_files_in_prompt = construct_analysis_prompt(code_files, selected_analyses)
 
544
  st.session_state.error_message = "Failed to generate analysis prompt."
545
  st.rerun()
546
 
547
+ # Display analysis results if available
548
  if st.session_state.analysis_requested:
549
  with results_placeholder:
550
  st.divider()
 
560
  elif not uploaded_file:
561
  results_placeholder.info("Upload a ZIP file to begin.")
562
 
563
+ # --- Audit Dashboard ---
564
+ # If analysis results exist, display an interactive dashboard summarizing key metrics.
565
+ if st.session_state.analysis_results:
566
+ st.subheader("Audit Dashboard")
567
+ # Compute metrics from session state and analysis results
568
+ metrics = {
569
+ "Files Analyzed": st.session_state.file_count,
570
+ "Total Tokens": estimate_token_count(st.session_state.total_chars)
571
+ }
572
+ metrics["Documentation Suggestions"] = len(st.session_state.analysis_results.get("documentation_suggestions", []))
573
+ metrics["Potential Bugs"] = len(st.session_state.analysis_results.get("potential_bugs", []))
574
+ metrics["Style Issues"] = len(st.session_state.analysis_results.get("style_issues", []))
575
+ metrics["Module Summaries"] = len(st.session_state.analysis_results.get("module_summaries", []))
576
+ metrics["Refactoring Suggestions"] = len(st.session_state.analysis_results.get("refactoring_suggestions", []))
577
+
578
+ # Create a DataFrame and a bar chart using Plotly
579
+ df_metrics = pd.DataFrame(list(metrics.items()), columns=["Metric", "Value"])
580
+ fig = px.bar(df_metrics, x="Metric", y="Value", title="Audit Summary Metrics")
581
+ st.plotly_chart(fig)
582
+
583
  results_placeholder.divider()
584
  results_placeholder.markdown("_Assistant powered by Google Gemini._")