Spaces:
Sleeping
Sleeping
| import streamlit as st | |
| from dotenv import load_dotenv | |
| import streamlit.components.v1 as components | |
| from analyzer import CodeAnalyzer | |
| from typing import Any, Iterable, List | |
| import os | |
| import html | |
| # Force reload environment variables | |
| load_dotenv(override=True) | |
| st.set_page_config( | |
| page_title="Matrix Code Analyzer", | |
| page_icon="🧠", | |
| layout="wide" | |
| ) | |
| # Debug sidebar removed for cleaner UI | |
| CUSTOM_CSS = """ | |
| <style> | |
| @import url('https://fonts.googleapis.com/css2?family=Share+Tech+Mono:wght@400&display=swap'); | |
| @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&display=swap'); | |
| @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&display=swap'); | |
| :root { | |
| --matrix-green: #00ff41; /* Brighter Matrix Green */ | |
| --matrix-blue: #0ccffa; | |
| --matrix-bg: #000000; /* Solid black for deeper matrix feel */ | |
| --text-main: #e7fceb; | |
| --text-muted: #94a9a0; | |
| --panel-bg: rgba(6, 16, 28, 0.55); | |
| --panel-border: rgba(0, 255, 65, 0.25); /* Green border for panels */ | |
| } | |
| .stApp { | |
| background: var(--matrix-bg); | |
| color: var(--text-main); | |
| font-family: 'Share Tech Mono', monospace; /* Default text to matrix font */ | |
| } | |
| .stApp::before { | |
| content: ''; | |
| position: fixed; | |
| inset: 0; | |
| background: linear-gradient(135deg, rgba(0, 255, 130, 0.05) 0%, transparent 55%), | |
| linear-gradient(225deg, rgba(12, 207, 250, 0.06) 0%, transparent 60%); | |
| z-index: -3; | |
| } | |
| .stApp::after { | |
| content: ''; | |
| position: fixed; | |
| inset: 0; | |
| background: radial-gradient(circle at 18% 20%, rgba(0, 255, 140, 0.08), transparent 58%), | |
| radial-gradient(circle at 78% 12%, rgba(12, 207, 250, 0.07), transparent 48%); | |
| z-index: -2; | |
| } | |
| [data-testid="stAppViewContainer"] { | |
| position: relative; | |
| z-index: 0; | |
| } | |
| html, body { | |
| overflow-y: auto !important; | |
| height: auto !important; | |
| } | |
| [data-testid="stAppViewContainer"] { | |
| overflow-y: auto !important; | |
| height: auto !important; | |
| } | |
| .stApp { | |
| overflow-y: auto !important; | |
| height: auto !important; | |
| } | |
| .block-container { | |
| overflow-y: visible !important; | |
| } | |
| /* Ensure main content can scroll */ | |
| [data-testid="stAppViewContainer"] > div { | |
| overflow-y: auto !important; | |
| height: auto !important; | |
| } | |
| /* Override any Streamlit default height constraints */ | |
| .stApp > div { | |
| height: auto !important; | |
| min-height: 100vh !important; | |
| } | |
| .matrix-rain { | |
| position: fixed; | |
| inset: 0; | |
| overflow: hidden; | |
| pointer-events: none; | |
| z-index: -1; | |
| } | |
| .matrix-rain span { | |
| position: absolute; | |
| top: -10%; | |
| color: rgba(0, 255, 65, 0.45); /* Brighter matrix rain */ | |
| font-family: 'Share Tech Mono', monospace; | |
| font-size: 14px; | |
| animation: matrixFall linear infinite; | |
| text-shadow: 0 0 10px rgba(0, 255, 65, 0.6); /* Stronger glow */ | |
| } | |
| @keyframes matrixFall { | |
| from { transform: translateY(-10%); opacity: 0; } | |
| 20% { opacity: 1; } | |
| to { transform: translateY(110vh); opacity: 0; } | |
| } | |
| [data-testid="stHeader"] { | |
| background: transparent; | |
| border-bottom: none; | |
| } | |
| .block-container { | |
| padding-top: 3rem; | |
| padding-bottom: 3.5rem; | |
| max-width: 1140px; | |
| } | |
| a { | |
| color: var(--matrix-green); | |
| text-decoration: none; | |
| } | |
| a:hover { | |
| text-decoration: underline; | |
| } | |
| .glass-panel { | |
| background: rgba(0, 0, 0, 0.7); | |
| border: 1px solid var(--panel-border); | |
| border-radius: 12px; /* Slightly less rounded for matrix look */ | |
| padding: 28px; | |
| backdrop-filter: blur(8px); /* Softer blur */ | |
| box-shadow: 0 0 25px rgba(0, 255, 65, 0.2); /* Green glow for panels */ | |
| margin-bottom: 1.6rem; | |
| } | |
| .hero { | |
| text-align: center; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .hero::before { | |
| content: ''; | |
| position: absolute; | |
| inset: 0; | |
| opacity: 0.3; | |
| background: linear-gradient(130deg, rgba(0, 255, 65, 0.15), rgba(0, 200, 255, 0.08)); | |
| pointer-events: none; | |
| } | |
| .hero-label { | |
| font-family: 'Orbitron', sans-serif; /* Use Orbitron for hero label */ | |
| font-size: 0.9rem; | |
| letter-spacing: 0.4em; | |
| text-transform: uppercase; | |
| color: var(--matrix-green); | |
| margin-bottom: 0.6rem; | |
| text-shadow: 0 0 8px rgba(0, 255, 65, 0.5); | |
| } | |
| .hero h1 { | |
| font-family: 'Orbitron', sans-serif; /* Use Orbitron for hero title */ | |
| margin-bottom: 0.6rem; | |
| font-size: clamp(2.5rem, 4vw, 3.8rem); | |
| font-weight: 700; | |
| letter-spacing: -0.05em; | |
| position: relative; | |
| z-index: 1; | |
| color: var(--matrix-green); | |
| text-shadow: 0 0 15px rgba(0, 255, 65, 0.8), 0 0 25px rgba(0, 255, 65, 0.6); | |
| } | |
| .hero .subline { | |
| font-family: 'Share Tech Mono', monospace; /* Use Share Tech Mono for subline */ | |
| color: rgba(231, 252, 235, 0.8); | |
| font-size: 1.1rem; | |
| max-width: 700px; | |
| margin: 0 auto; | |
| line-height: 1.7; | |
| position: relative; | |
| z-index: 1; | |
| } | |
| .hero-badges { | |
| display: flex; | |
| gap: 0.85rem; | |
| justify-content: center; | |
| flex-wrap: wrap; | |
| margin-top: 2rem; | |
| position: relative; | |
| z-index: 1; | |
| } | |
| .hero-badge { | |
| padding: 0.4rem 1rem; | |
| border-radius: 999px; | |
| background: rgba(0, 255, 65, 0.15); | |
| border: 1px solid rgba(0, 255, 65, 0.4); | |
| color: var(--matrix-green); | |
| font-size: 0.9rem; | |
| font-weight: 500; | |
| text-shadow: 0 0 5px rgba(0, 255, 65, 0.4); | |
| } | |
| .body-text { | |
| color: var(--text-main); | |
| font-family: 'Share Tech Mono', monospace; | |
| font-size: 1rem; | |
| line-height: 1.7; | |
| margin: 0; | |
| } | |
| .settings-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); | |
| gap: 1.2rem; | |
| } | |
| .stSelectbox > div > div { | |
| background: rgba(0, 0, 0, 0.8); | |
| border-radius: 10px; | |
| border: 1px solid rgba(0, 255, 65, 0.3); | |
| color: var(--text-main); | |
| font-family: 'Share Tech Mono', monospace; | |
| } | |
| .stSelectbox label, .stTextInput label, .stFileUploader label { | |
| font-family: 'Orbitron', sans-serif; | |
| font-weight: 600; | |
| color: var(--matrix-green); | |
| text-shadow: 0 0 5px rgba(0, 255, 65, 0.3); | |
| } | |
| .stTextInput > div > div > input { | |
| border-radius: 10px; | |
| background: rgba(0, 0, 0, 0.8); | |
| border: 1px solid rgba(0, 255, 65, 0.25); | |
| color: var(--text-main); | |
| font-family: 'Share Tech Mono', monospace; | |
| } | |
| textarea { | |
| border-radius: 12px !important; | |
| background: rgba(0, 0, 0, 0.85) !important; | |
| border: 1px solid rgba(0, 255, 65, 0.3) !important; | |
| color: var(--text-main) !important; | |
| font-family: 'Share Tech Mono', monospace !important; | |
| resize: vertical; | |
| } | |
| textarea::placeholder, | |
| .stTextInput > div > div > input::placeholder { | |
| color: rgba(0, 255, 65, 0.5) !important; | |
| } | |
| .stFileUploader > div { | |
| border-radius: 12px !important; | |
| border: 2px dashed rgba(0, 255, 65, 0.5) !important; | |
| background: rgba(0, 0, 0, 0.7) !important; | |
| padding: 1.5rem 2rem !important; | |
| } | |
| .stButton > button { | |
| height: 50px; | |
| border-radius: 12px; | |
| font-weight: 700; | |
| letter-spacing: 0.05em; | |
| background: linear-gradient(120deg, var(--matrix-green), rgba(0, 200, 255, 0.8)); | |
| color: #000; | |
| border: none; | |
| box-shadow: 0 0 20px rgba(0, 255, 65, 0.6); | |
| text-transform: uppercase; | |
| transition: all 0.3s ease; | |
| } | |
| .stButton > button:hover { | |
| transform: translateY(-2px); | |
| box-shadow: 0 0 35px rgba(0, 255, 65, 0.9); | |
| background: linear-gradient(120deg, rgba(0, 255, 65, 0.95), rgba(0, 220, 255, 0.95)); | |
| } | |
| .stTabs [data-baseweb="tab-list"] { | |
| background: rgba(0, 0, 0, 0.6); | |
| border-radius: 12px; | |
| padding: 0.5rem; | |
| border: 1px solid rgba(0, 255, 65, 0.2); | |
| } | |
| .stTabs [data-baseweb="tab"] { | |
| border-radius: 8px; | |
| font-weight: 500; | |
| color: rgba(0, 255, 65, 0.7); | |
| font-family: 'Orbitron', sans-serif; | |
| padding: 0.75rem 1.2rem; | |
| } | |
| .stTabs [aria-selected="true"] { | |
| background: rgba(0, 255, 65, 0.1); | |
| color: var(--matrix-green); | |
| text-shadow: 0 0 5px rgba(0, 255, 65, 0.5); | |
| } | |
| .stTabs [data-baseweb="tab-panel"] { | |
| background: rgba(0, 0, 0, 0.75); | |
| border: 1px solid rgba(0, 255, 65, 0.2); | |
| border-radius: 12px; | |
| padding: 28px; | |
| margin-top: 1.5rem; | |
| backdrop-filter: blur(8px); | |
| box-shadow: 0 0 25px rgba(0, 255, 65, 0.2); | |
| } | |
| .stats-grid { | |
| display: grid; | |
| grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); | |
| gap: 1.2rem; | |
| } | |
| .stats-card { | |
| background: rgba(0, 0, 0, 0.6); | |
| border: 1px solid rgba(0, 255, 65, 0.15); | |
| border-radius: 12px; | |
| padding: 1.5rem; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 0.4rem; | |
| box-shadow: 0 0 15px rgba(0, 255, 65, 0.1); | |
| } | |
| .stats-card .label { | |
| color: rgba(0, 255, 65, 0.8); | |
| font-size: 0.8rem; | |
| letter-spacing: 0.1em; | |
| text-transform: uppercase; | |
| font-family: 'Share Tech Mono', monospace; | |
| } | |
| .stats-card .value { | |
| font-family: 'Orbitron', sans-serif; | |
| font-size: 1.9rem; | |
| font-weight: 700; | |
| color: var(--matrix-green); | |
| text-shadow: 0 0 10px rgba(0, 255, 65, 0.5); | |
| } | |
| .stats-card .status { | |
| font-size: 0.95rem; | |
| color: rgba(0, 255, 65, 0.7); | |
| } | |
| .list-card { | |
| background: rgba(0, 0, 0, 0.65); | |
| border: 1px solid rgba(0, 255, 65, 0.18); | |
| border-radius: 12px; | |
| padding: 1.5rem 1.8rem; | |
| margin-bottom: 1.2rem; | |
| box-shadow: 0 0 18px rgba(0, 255, 65, 0.15); | |
| } | |
| .list-card h3 { | |
| font-family: 'Orbitron', sans-serif; | |
| margin-bottom: 1rem; | |
| font-size: 1.2rem; | |
| font-weight: 600; | |
| display: flex; | |
| align-items: center; | |
| gap: 0.7rem; | |
| color: var(--matrix-green); | |
| text-shadow: 0 0 8px rgba(0, 255, 65, 0.4); | |
| } | |
| .list-card ul { | |
| margin: 0; | |
| padding-left: 1.2rem; | |
| color: rgba(231, 252, 235, 0.9); | |
| font-family: 'Share Tech Mono', monospace; | |
| display: grid; | |
| gap: 0.6rem; | |
| } | |
| .list-card li { | |
| line-height: 1.5; | |
| position: relative; | |
| } | |
| .list-card li::before { | |
| content: '•'; /* Use a bullet point for simple list styling */ | |
| position: absolute; | |
| left: -1rem; | |
| color: var(--matrix-green); | |
| font-size: 1.2em; | |
| line-height: 1; | |
| text-shadow: 0 0 5px rgba(0, 255, 65, 0.7); | |
| } | |
| .model-chip { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 0.6rem; | |
| padding: 0.6rem 1rem; | |
| border-radius: 999px; | |
| background: rgba(0, 255, 65, 0.1); | |
| border: 1px solid rgba(0, 255, 65, 0.3); | |
| font-size: 0.95rem; | |
| color: var(--matrix-green); | |
| margin-bottom: 1.5rem; | |
| font-family: 'Orbitron', sans-serif; | |
| text-shadow: 0 0 6px rgba(0, 255, 65, 0.4); | |
| } | |
| .file-meta { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 1rem; | |
| margin-top: 0.6rem; | |
| font-size: 0.9rem; | |
| color: rgba(231, 252, 235, 0.7); | |
| font-family: 'Share Tech Mono', monospace; | |
| } | |
| .file-meta span { | |
| padding: 0.4rem 0.9rem; | |
| border-radius: 999px; | |
| background: rgba(0, 255, 65, 0.08); | |
| border: 1px solid rgba(0, 255, 65, 0.2); | |
| text-shadow: 0 0 4px rgba(0, 255, 65, 0.2); | |
| } | |
| .stAlert { | |
| border-radius: 12px; | |
| border: 1px solid rgba(0, 255, 65, 0.3); | |
| background: rgba(0, 0, 0, 0.75); | |
| color: var(--text-main); | |
| font-family: 'Share Tech Mono', monospace; | |
| box-shadow: 0 0 15px rgba(0, 255, 65, 0.25); | |
| } | |
| .streamlit-expanderHeader { | |
| background: rgba(0, 0, 0, 0.6); | |
| border-radius: 12px; | |
| border: 1px solid rgba(0, 255, 65, 0.2); | |
| color: var(--matrix-green); | |
| font-family: 'Orbitron', sans-serif; | |
| text-shadow: 0 0 5px rgba(0, 255, 65, 0.3); | |
| } | |
| .stSpinner > div { | |
| border-top-color: var(--matrix-green) !important; | |
| border-right-color: var(--matrix-blue) !important; | |
| } | |
| @media (max-width: 768px) { | |
| .block-container { | |
| padding-left: 1rem; | |
| padding-right: 1rem; | |
| } | |
| .glass-panel, .stTabs [data-baseweb="tab-panel"] { | |
| padding: 18px; | |
| } | |
| .stats-grid { | |
| grid-template-columns: 1fr; | |
| } | |
| .hero h1 { | |
| font-size: 2.5rem; | |
| } | |
| .hero-badges { | |
| gap: 0.6rem; | |
| } | |
| .hero-badge { | |
| font-size: 0.8rem; | |
| padding: 0.3rem 0.7rem; | |
| } | |
| } | |
| </style> | |
| """ | |
| st.markdown(CUSTOM_CSS, unsafe_allow_html=True) | |
| st.markdown( | |
| """ | |
| <div class="matrix-rain" id="matrix-rain"></div> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| def get_analyzer(): | |
| # Use /tmp for cache in Docker/HF Spaces (always writable) | |
| # Use .analyzer_cache for local development | |
| cache_dir = "/tmp/.analyzer_cache" if os.path.exists("/app") else ".analyzer_cache" | |
| return CodeAnalyzer(cache_dir=cache_dir) | |
| analyzer = get_analyzer() | |
| AVAILABLE_MODELS = analyzer.available_models | |
| if not AVAILABLE_MODELS: | |
| st.error("No AI models configured. Add API keys to your .env file and restart the app.") | |
| st.stop() | |
| LANGUAGE_DISPLAY = { | |
| "auto": "Auto Detect", | |
| "python": "Python", | |
| "javascript": "JavaScript", | |
| "typescript": "TypeScript", | |
| "html": "HTML", | |
| "css": "CSS", | |
| "java": "Java", | |
| "cpp": "C++", | |
| "c": "C", | |
| "csharp": "C#", | |
| "go": "Go", | |
| "rust": "Rust", | |
| "php": "PHP", | |
| "ruby": "Ruby", | |
| "swift": "Swift", | |
| "kotlin": "Kotlin", | |
| } | |
| LANGUAGE_OPTIONS = list(LANGUAGE_DISPLAY.keys()) | |
| def ensure_list(items: Any) -> List[str]: | |
| if not items: | |
| return [] | |
| if isinstance(items, str): | |
| clean = items.strip() | |
| return [clean] if clean else [] | |
| if isinstance(items, dict): | |
| return [f"{key}: {value}" for key, value in items.items() if str(value).strip()] | |
| if isinstance(items, Iterable): | |
| values = [] | |
| for entry in items: | |
| if entry is None: | |
| continue | |
| text = str(entry).strip() | |
| if text: | |
| values.append(text) | |
| return values | |
| return [str(items)] | |
| def parse_score(raw: Any) -> float: | |
| try: | |
| return float(raw) | |
| except (TypeError, ValueError): | |
| return 0.0 | |
| def score_badge(score: float) -> tuple[str, str]: | |
| if score >= 80: | |
| return "Excellent", "#00fba4" | |
| if score >= 60: | |
| return "Review Suggested", "#ffd76a" | |
| return "Needs Attention", "#ff6b6b" | |
| def render_list_section(title: str, icon: str, content: Any, fallback: str | None = None) -> None: | |
| entries = ensure_list(content) | |
| if entries: | |
| items_html = "".join(f"<li>{html.escape(entry)}</li>" for entry in entries[:6]) | |
| st.markdown( | |
| f""" | |
| <div class="list-card"> | |
| <h3>{icon} {title}</h3> | |
| <ul>{items_html}</ul> | |
| </div> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| elif fallback: | |
| st.markdown( | |
| f""" | |
| <div class="list-card"> | |
| <h3>{icon} {title}</h3> | |
| <p class="body-text">{html.escape(fallback)}</p> | |
| </div> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| def render_code_result(result: dict[str, Any], model_label: str) -> None: | |
| if result.get("error"): | |
| st.error(f"Analysis failed: {result['error']}") | |
| return | |
| score = parse_score(result.get("quality_score", 0)) | |
| status_label, status_color = score_badge(score) | |
| language = (result.get("language") or "auto").upper() | |
| line_count = result.get("line_count", "-") | |
| exec_time = parse_score(result.get("execution_time", 0.0)) | |
| cached_text = "Hit" if result.get("cached") else "Fresh" | |
| stats = [ | |
| {"label": "Quality Score", "value": f"{int(round(score))}/100", "sub": status_label, "color": status_color}, | |
| {"label": "Language", "value": language, "sub": "Detected" if language != "AUTO" else "Auto"}, | |
| {"label": "Lines", "value": line_count, "sub": "Analyzed"}, | |
| {"label": "Latency", "value": f"{exec_time:.1f}s", "sub": "Runtime"}, | |
| {"label": "Cache", "value": cached_text, "sub": "Result Store"}, | |
| ] | |
| # Render stats using native Streamlit components to avoid raw HTML showing | |
| st.markdown( | |
| f""" | |
| <div class="glass-panel"> | |
| <div class="model-chip">🤖 {model_label}</div> | |
| </div> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| cols = st.columns(len(stats)) | |
| for idx, stat in enumerate(stats): | |
| with cols[idx]: | |
| st.metric(label=stat["label"], value=stat["value"], delta=stat["sub"]) | |
| summary = result.get("summary") | |
| if summary: | |
| st.markdown( | |
| f""" | |
| <div class="glass-panel"> | |
| <h3>📋 Overview</h3> | |
| <p class="body-text">{html.escape(summary)}</p> | |
| </div> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| render_list_section("Highlights", "✨", result.get("strengths")) | |
| render_list_section("Bug Detection", "🐞", result.get("bugs") or result.get("issues"), "No critical bugs were flagged.") | |
| render_list_section("Security", "🔒", result.get("security_vulnerabilities") or result.get("security_concerns"), "No security vulnerabilities detected.") | |
| render_list_section("Code Quality", "🧩", result.get("quality_issues"), "Structure looks solid and maintainable.") | |
| render_list_section("Quick Fixes", "⚡", result.get("quick_fixes"), "No urgent fixes suggested.") | |
| render_list_section("Suggestions", "💡", result.get("suggestions")) | |
| raw = result.get("raw_response") | |
| if raw: | |
| with st.expander("View full model response", expanded=False): | |
| st.code(raw, language="text") | |
| def render_repo_result(result: dict[str, Any], model_label: str) -> None: | |
| if result.get("error"): | |
| st.error(f"Repository analysis failed: {result['error']}") | |
| return | |
| info = result.get("repository_info", {}) | |
| repo_name = info.get("name", "Repository") | |
| repo_desc = info.get("description") or "No description provided." | |
| repo_url = result.get("repo_url") or st.session_state.get("repo_analysis_url") | |
| repo_stats = [ | |
| {"label": "Primary Language", "value": info.get("language", "Unknown"), "sub": "Detected"}, | |
| {"label": "Stars", "value": info.get("stars", 0), "sub": "Community"}, | |
| {"label": "Forks", "value": info.get("forks", 0), "sub": "Collaboration"}, | |
| {"label": "Size", "value": f"{info.get('size', 0)} KB", "sub": "Repo Size"}, | |
| {"label": "Latency", "value": f"{parse_score(result.get('execution_time', 0.0)):.1f}s", "sub": "Runtime"}, | |
| ] | |
| link_html = f'<p class="body-text"><a href="{repo_url}" target="_blank">View repository ↗</a></p>' if repo_url else "" | |
| # Header card | |
| st.markdown( | |
| f""" | |
| <div class="glass-panel"> | |
| <div class="model-chip">🤖 {html.escape(model_label)}</div> | |
| <h2 style="margin-bottom:0.35rem;">{html.escape(repo_name)}</h2> | |
| <p class="body-text" style="color: var(--text-muted); margin-bottom:1.2rem;">{html.escape(repo_desc)}</p> | |
| {link_html} | |
| </div> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| # Metrics row | |
| cols = st.columns(len(repo_stats)) | |
| for idx, stat in enumerate(repo_stats): | |
| with cols[idx]: | |
| st.metric(label=stat["label"], value=stat["value"], delta=stat["sub"]) | |
| overview = result.get("project_overview") | |
| if overview: | |
| st.markdown( | |
| f""" | |
| <div class="glass-panel"> | |
| <h3>📋 Project Overview</h3> | |
| <p class="body-text">{html.escape(overview)}</p> | |
| </div> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| render_list_section("Architecture Quality", "🏗️", result.get("architecture_quality"), "Project structure looks well organized.") | |
| render_list_section("Critical Issues", "🚨", result.get("critical_issues"), "No critical issues were highlighted.") | |
| render_list_section("Priority Improvements", "🎯", result.get("improvement_priorities"), "No immediate improvements suggested.") | |
| render_list_section("Onboarding Guide", "🚀", result.get("onboarding_guide"), "No specific onboarding steps identified.") | |
| render_list_section("Tech Stack", "🛠️", result.get("tech_stack_rationale"), "Tech stack details were not identified.") | |
| render_list_section("API Endpoints", "🔌", result.get("api_endpoint_summary"), "No API endpoints were identified.") | |
| raw = result.get("raw_response") | |
| if raw: | |
| with st.expander("View full model response", expanded=False): | |
| st.code(raw, language="text") | |
| if "code_input" not in st.session_state: | |
| st.session_state.code_input = "" | |
| if "code_file_meta" not in st.session_state: | |
| st.session_state.code_file_meta = None | |
| if "code_analysis_result" not in st.session_state: | |
| st.session_state.code_analysis_result = None | |
| if "code_analysis_model" not in st.session_state: | |
| st.session_state.code_analysis_model = "" | |
| if "repo_analysis_result" not in st.session_state: | |
| st.session_state.repo_analysis_result = None | |
| if "repo_analysis_model" not in st.session_state: | |
| st.session_state.repo_analysis_model = "" | |
| if "repo_analysis_url" not in st.session_state: | |
| st.session_state.repo_analysis_url = "" | |
| st.markdown( | |
| """ | |
| <div class="glass-panel hero"> | |
| <div class="hero-label">CODE ANALYZER</div> | |
| <h1>AI Code Analyzer</h1> | |
| <p class="subline">Inspect bugs, surface security gaps, and review repositories with instant feedback.</p> | |
| <div class="hero-badges"> | |
| <span class="hero-badge">🧠 Multi-model</span> | |
| <span class="hero-badge">🔍 Bug & Security Scan</span> | |
| <span class="hero-badge">⚡ Instant Results</span> | |
| <span class="hero-badge">📦 GitHub Ready</span> | |
| </div> | |
| </div> | |
| <script> | |
| (function drizzle() { | |
| const container = document.getElementById('matrix-rain'); | |
| if (!container || container.dataset.initialized) return; | |
| container.dataset.initialized = 'true'; | |
| const glyphs = "01ΛΣΞ∑¥$#@*&%=+"; | |
| const nodeCount = 80; | |
| for (let i = 0; i < nodeCount; i++) { | |
| const drop = document.createElement('span'); | |
| drop.textContent = glyphs[Math.floor(Math.random() * glyphs.length)]; | |
| drop.style.left = Math.random() * 100 + '%'; | |
| drop.style.animationDuration = (Math.random() * 4 + 3) + 's'; | |
| drop.style.animationDelay = (Math.random() * 4) + 's'; | |
| container.appendChild(drop); | |
| } | |
| })(); | |
| </script> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| code_tab, repo_tab = st.tabs(["Code Analysis", "Repository Insights"]) | |
| with code_tab: | |
| model_keys = list(AVAILABLE_MODELS.keys()) | |
| selected_model_code = st.selectbox( | |
| "AI Model", | |
| options=model_keys, | |
| format_func=lambda key: AVAILABLE_MODELS[key], | |
| key="code_model_select", | |
| ) | |
| selected_language = st.selectbox( | |
| "Language", | |
| LANGUAGE_OPTIONS, | |
| format_func=lambda code: LANGUAGE_DISPLAY[code], | |
| key="language_select", | |
| ) | |
| uploaded_file = st.file_uploader( | |
| "Upload a code file", | |
| type=["py", "js", "java", "cpp", "c", "cs", "go", "rs", "php", "rb", "swift", "kt", "txt"], | |
| key="code_file_uploader", | |
| ) | |
| if uploaded_file is not None: | |
| raw_bytes = uploaded_file.read() | |
| try: | |
| decoded = raw_bytes.decode("utf-8") | |
| except UnicodeDecodeError: | |
| st.error("Only UTF-8 encoded files are supported.") | |
| else: | |
| st.session_state.code_input = decoded | |
| st.session_state.code_file_meta = { | |
| "name": uploaded_file.name, | |
| "size": len(raw_bytes), | |
| "lines": len(decoded.splitlines()), | |
| } | |
| st.text_area( | |
| "Or paste code below", | |
| key="code_input", | |
| height=320, | |
| placeholder="Paste any code snippet to inspect bugs, security gaps, and quality issues.", | |
| ) | |
| meta = st.session_state.get("code_file_meta") | |
| if meta: | |
| st.markdown( | |
| f""" | |
| <div class="list-card"> | |
| <h3> Active File</h3> | |
| <div class="file-meta"> | |
| <span>{meta['name']}</span> | |
| <span>{meta['lines']} lines</span> | |
| <span>{meta['size']} bytes</span> | |
| </div> | |
| </div> | |
| """, | |
| unsafe_allow_html=True, | |
| ) | |
| analyze_code_clicked = st.button("Run Code Analysis", key="code_analyze_button", use_container_width=True) | |
| if analyze_code_clicked: | |
| snippet = st.session_state.get("code_input", "").strip() | |
| if not snippet: | |
| st.error("Please upload a file or paste some code to analyze.") | |
| else: | |
| with st.spinner("Analyzing code..."): | |
| language_arg = None if selected_language == "auto" else selected_language | |
| result = analyzer.analyze_code(snippet, selected_model_code, language_arg) | |
| st.session_state.code_analysis_result = result | |
| st.session_state.code_analysis_model = AVAILABLE_MODELS[selected_model_code] | |
| if st.session_state.get("code_analysis_result"): | |
| render_code_result(st.session_state.code_analysis_result, st.session_state.get("code_analysis_model", "")) | |
| else: | |
| st.info("Upload a file or paste code to generate an analysis.") | |
| with repo_tab: | |
| model_keys = list(AVAILABLE_MODELS.keys()) | |
| selected_model_repo = st.selectbox( | |
| "AI Model", | |
| options=model_keys, | |
| format_func=lambda key: AVAILABLE_MODELS[key], | |
| key="repo_model_select", | |
| ) | |
| st.text_input( | |
| "GitHub repository URL", | |
| placeholder="https://github.com/owner/repo", | |
| key="repo_analysis_url", | |
| ) | |
| analyze_repo_clicked = st.button("Analyze Repository", key="repo_analyze_button", use_container_width=True) | |
| if analyze_repo_clicked: | |
| repo_url = st.session_state.get("repo_analysis_url", "").strip() | |
| if not repo_url: | |
| st.error("Enter a GitHub repository URL.") | |
| else: | |
| with st.spinner("Inspecting repository..."): | |
| result = analyzer.analyze_github_repo(repo_url, selected_model_repo) | |
| st.session_state.repo_analysis_result = result | |
| st.session_state.repo_analysis_model = AVAILABLE_MODELS[selected_model_repo] | |
| if st.session_state.get("repo_analysis_result"): | |
| render_repo_result( | |
| st.session_state.repo_analysis_result, | |
| st.session_state.get("repo_analysis_model", ""), | |
| ) | |
| else: | |
| st.info("Provide a public GitHub repository URL to review its structure, issues, and improvements.") | |