|
import streamlit as st
|
|
import tempfile
|
|
import json
|
|
from backend import (
|
|
clone_repository,
|
|
read_code_files,
|
|
analyze_code,
|
|
check_api_keys
|
|
)
|
|
|
|
def get_severity_color(severity):
|
|
"""Get color based on severity level."""
|
|
colors = {
|
|
"LOW": "#FFA500",
|
|
"MEDIUM": "#FF6B6B",
|
|
"HIGH": "#FF0000"
|
|
}
|
|
return colors.get(severity.upper(), "#000000")
|
|
|
|
def render_analysis_results(analysis_text):
|
|
"""Render the analysis results according to the Pydantic model schema."""
|
|
try:
|
|
|
|
analysis_data = json.loads(analysis_text)
|
|
|
|
|
|
st.markdown("""
|
|
<style>
|
|
.severity-box {
|
|
background-color: #f0f2f6;
|
|
padding: 1rem;
|
|
border-radius: 0.5rem;
|
|
margin: 1rem 0;
|
|
}
|
|
.file-impact {
|
|
background-color: #ffffff;
|
|
padding: 1rem;
|
|
border-radius: 0.5rem;
|
|
margin: 0.5rem 0;
|
|
border: 1px solid #e1e4e8;
|
|
}
|
|
.impact-count {
|
|
background-color: #e6f3ff;
|
|
padding: 0.5rem 1rem;
|
|
border-radius: 0.5rem;
|
|
margin: 1rem 0;
|
|
}
|
|
</style>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
severity_level = analysis_data['severity_level']
|
|
if(analysis_data['number_of_files_impacted'] == None or analysis_data['number_of_files_impacted'] == 0):
|
|
severity_level = "No Impact"
|
|
elif(analysis_data['number_of_files_impacted'] > 0 and analysis_data['number_of_files_impacted'] <= 3):
|
|
severity_level = "Low"
|
|
elif(analysis_data['number_of_files_impacted'] > 3 and analysis_data['number_of_files_impacted'] <= 8):
|
|
severity_level = "Medium"
|
|
else:
|
|
severity_level = "High"
|
|
|
|
|
|
severity_color = get_severity_color(severity_level)
|
|
st.markdown(f"""
|
|
<div class="severity-box">
|
|
<h3 style='color: {severity_color}; margin: 0; font-size: 1.5rem; font-weight: bold;'>
|
|
Severity Level: {severity_level}
|
|
</h3>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
st.markdown(f"""
|
|
<div class="impact-count">
|
|
<h3 style='color: #1f77b4; margin: 0; font-size: 1.2rem;'>
|
|
Number of Files Impacted: {analysis_data['number_of_files_impacted']}
|
|
</h3>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
|
|
|
|
st.markdown("<h3 style='color: #2c3e50; font-size: 1.3rem;'>Files Impacted</h3>", unsafe_allow_html=True)
|
|
|
|
for file_impact in analysis_data['files_impacted']:
|
|
with st.expander(f"📄 {file_impact['files_impacted']}", expanded=False):
|
|
st.markdown(f"""
|
|
<div class="file-impact">
|
|
<p style='color: #34495e; font-size: 1rem; line-height: 1.6;'>
|
|
{file_impact['impact_details']}
|
|
</p>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
st.markdown(analysis_text)
|
|
except Exception as e:
|
|
st.error(f"Error rendering analysis results: {str(e)}")
|
|
st.markdown(analysis_text)
|
|
|
|
def main():
|
|
st.title("Git Repository Code Analyzer")
|
|
st.write("Enter a Git repository URL and a prompt to analyze the code.")
|
|
|
|
|
|
examples = [
|
|
{
|
|
"Git URL": "https://github.com/kedar-bhumkar/SFRoutingFramework",
|
|
"Code/Config Changes": "Enum USER_INTERFACE removed from file: BaseAppLiterals.cls"
|
|
},
|
|
{
|
|
"Git URL": "https://github.com/kedar-bhumkar/SFDynamicFields",
|
|
"Code/Config Changes": "Removed a field Value__c from DynamicFieldTable__c.object"
|
|
|
|
}
|
|
]
|
|
|
|
|
|
if 'selected_example' not in st.session_state:
|
|
st.session_state.selected_example = None
|
|
if 'openai_key' not in st.session_state:
|
|
st.session_state.openai_key = ""
|
|
|
|
|
|
with st.expander("🔑 API Key Settings", expanded=False):
|
|
st.markdown("""
|
|
<style>
|
|
.api-key-section {
|
|
background-color: #f8f9fa;
|
|
padding: 1rem;
|
|
border-radius: 0.5rem;
|
|
margin: 0.5rem 0;
|
|
}
|
|
</style>
|
|
""", unsafe_allow_html=True)
|
|
|
|
st.markdown("""
|
|
<div class="api-key-section">
|
|
<p style='color: #2c3e50; font-size: 0.9rem;'>
|
|
Enter your OpenAI API key to use the GPT-4 model. The key will be stored in the session and not saved permanently.
|
|
</p>
|
|
</div>
|
|
""", unsafe_allow_html=True)
|
|
|
|
openai_key = st.text_input(
|
|
"OpenAI API Key",
|
|
value=st.session_state.openai_key,
|
|
type="password",
|
|
help="Enter your OpenAI API key to use GPT-4"
|
|
)
|
|
|
|
if openai_key:
|
|
st.session_state.openai_key = openai_key
|
|
st.success("API key saved for this session")
|
|
|
|
|
|
st.subheader("Example Cases")
|
|
|
|
|
|
col1, col2, col3 = st.columns([2, 2, 1])
|
|
|
|
|
|
with col1:
|
|
st.write("**Git URL**")
|
|
with col2:
|
|
st.write("**Code/Config Changes**")
|
|
with col3:
|
|
st.write("**Action**")
|
|
|
|
|
|
for idx, example in enumerate(examples):
|
|
with col1:
|
|
st.write(example["Git URL"])
|
|
with col2:
|
|
st.write(example["Code/Config Changes"])
|
|
with col3:
|
|
if st.button("Select", key=f"select_{idx}"):
|
|
st.session_state.selected_example = idx
|
|
st.session_state.repo_url = example["Git URL"]
|
|
st.session_state.prompt = example["Code/Config Changes"]
|
|
st.experimental_rerun()
|
|
|
|
|
|
repo_url = st.text_input("Git Repository URL",
|
|
value=st.session_state.get("repo_url", ""))
|
|
|
|
|
|
model = st.selectbox(
|
|
"Select AI Model",
|
|
["gpt-4", "claude-sonnet (coming soon)"],
|
|
help="Choose the AI model to analyze the code"
|
|
)
|
|
|
|
prompt = st.text_area("Code or configuration changes",
|
|
value=st.session_state.get("prompt", "List down the code/configuration changes to be performed"))
|
|
|
|
|
|
if st.button("Clear Selection"):
|
|
st.session_state.selected_example = None
|
|
st.session_state.repo_url = ""
|
|
st.session_state.prompt = "List down the code/configuration changes to be performed"
|
|
st.experimental_rerun()
|
|
|
|
if st.button("Analyze"):
|
|
if not repo_url:
|
|
st.error("Please enter a Git repository URL")
|
|
return
|
|
|
|
|
|
api_keys_status = check_api_keys()
|
|
if model == "gpt-4":
|
|
|
|
if st.session_state.openai_key:
|
|
|
|
api_keys_status["gpt-4"] = True
|
|
elif not api_keys_status["gpt-4"]:
|
|
st.error("OpenAI API key not found. Please enter your key in the API Key Settings section or set the OPENAI_API_KEY environment variable.")
|
|
return
|
|
elif model == "claude-sonnet" and not api_keys_status["claude-sonnet"]:
|
|
st.error("Anthropic API key not found. Please set the ANTHROPIC_API_KEY environment variable.")
|
|
return
|
|
|
|
with st.spinner("Cloning repository and analyzing code..."):
|
|
|
|
with tempfile.TemporaryDirectory() as temp_dir:
|
|
|
|
success, error = clone_repository(repo_url, temp_dir)
|
|
if not success:
|
|
st.error(f"Error cloning repository: {error}")
|
|
return
|
|
|
|
|
|
code_files, warnings = read_code_files(temp_dir)
|
|
|
|
|
|
for warning in warnings:
|
|
st.warning(warning)
|
|
|
|
if not code_files:
|
|
st.warning("No code files found in the repository.")
|
|
return
|
|
|
|
|
|
analysis, error = analyze_code(code_files, prompt, model)
|
|
|
|
if error:
|
|
st.error(f"Error during analysis: {error}")
|
|
return
|
|
|
|
if analysis:
|
|
st.subheader("Analysis Results")
|
|
render_analysis_results(analysis)
|
|
|
|
if __name__ == "__main__":
|
|
main() |