Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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 |
-
|
| 12 |
-
MAX_PROMPT_TOKENS_ESTIMATE = 800000 # Keep this estimate
|
| 13 |
RESULTS_PAGE_SIZE = 25
|
| 14 |
|
| 15 |
-
AVAILABLE_ANALYSES = {
|
| 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 |
-
}
|
| 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 #
|
| 39 |
if 'available_models_dict' not in st.session_state:
|
| 40 |
-
st.session_state.available_models_dict = {} #
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
# --- Gemini API Setup & Model Discovery ---
|
| 43 |
model = None # Global variable for the initialized model instance
|
| 44 |
|
| 45 |
-
|
| 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 {}
|
| 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()
|
| 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
|
| 89 |
st.stop()
|
| 90 |
return False
|
| 91 |
elif st.session_state.mock_api_call:
|
| 92 |
-
return True
|
| 93 |
elif model is not None and model.model_name == selected_name:
|
| 94 |
-
return True
|
| 95 |
elif model is not None and model.model_name != selected_name:
|
| 96 |
print("Model changed. Re-initializing...")
|
| 97 |
-
model = None
|
| 98 |
-
return initialize_gemini_model()
|
| 99 |
elif not selected_name and not st.session_state.mock_api_call:
|
| 100 |
-
|
| 101 |
-
|
| 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,
|
| 110 |
-
If an integer is provided (e.g., total character count),
|
| 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
|
| 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
|
| 178 |
-
Returns the full prompt and
|
| 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
|
| 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."
|
| 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
|
| 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
|
| 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
|
| 455 |
else:
|
| 456 |
st.warning("Add GEMINI_API_KEY to secrets to list models.")
|
| 457 |
st.session_state.selected_model_name = None
|
| 458 |
-
else:
|
| 459 |
st.info("Mock API Mode ACTIVE")
|
| 460 |
-
st.session_state.selected_model_name = "mock_model"
|
| 461 |
-
|
| 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
|
| 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
|
| 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._")
|