ishaAtif/python-code-reviewer
0
1import json2import time3import streamlit as st4from code_editor import code_editor5from reviewer import review_code6 7# ── Page config ───────────────────────────────────────────────────────────────8st.set_page_config(page_title="Python Code Reviewer", page_icon="🐍", layout="wide")9 10# ── Usage protection ──────────────────────────────────────────────────────────11MAX_REQUESTS_PER_SESSION = 512COOLDOWN_SECONDS = 1513MAX_CODE_LENGTH = 600014 15if "request_count" not in st.session_state:16 st.session_state.request_count = 017 18if "last_request_time" not in st.session_state:19 st.session_state.last_request_time = 020 21# ── Custom CSS ────────────────────────────────────────────────────────────────22st.markdown(23 """24<style>25 .stApp {26 background: linear-gradient(135deg, #0b1020 0%, #161b33 45%, #1f2550 100%);27 color: #f8fafc;28 }29 30 .block-container {31 max-width: 1100px;32 padding-top: 2rem;33 padding-bottom: 3rem;34 }35 36 .hero {37 text-align: center;38 padding: 1.2rem 0 2rem 0;39 }40 41 .main-title {42 font-size: 3.2rem;43 font-weight: 800;44 margin-bottom: 0.4rem;45 background: linear-gradient(90deg, #ff4ecd, #7c5cff);46 -webkit-background-clip: text;47 -webkit-text-fill-color: transparent;48 }49 50 .subtitle {51 color: #cbd5e1;52 font-size: 1.08rem;53 line-height: 1.7;54 max-width: 760px;55 margin: 0 auto;56 }57 58 .section-heading {59 font-size: 2rem;60 font-weight: 800;61 color: #ffffff;62 margin-top: 1rem;63 margin-bottom: 0.8rem;64 }65 66 .card {67 background: rgba(255, 255, 255, 0.06);68 border: 1px solid rgba(255, 255, 255, 0.10);69 border-radius: 18px;70 padding: 1.25rem;71 margin-top: 1rem;72 margin-bottom: 1rem;73 box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);74 }75 76 .section-title {77 font-size: 1.15rem;78 font-weight: 700;79 margin-bottom: 1rem;80 letter-spacing: 0.2px;81 }82 83 .bug-title { color: #ff7b7b; }84 .complex-title { color: #ffd54a; }85 .oop-title { color: #68e0a1; }86 .rewrite-title { color: #69a8ff; }87 88 .helper-text {89 color: #dbe4f0;90 font-size: 0.97rem;91 line-height: 1.8;92 }93 94 /* Text area */95 .stTextArea textarea {96 background: #07122b !important;97 color: #f8fafc !important;98 border: 1px solid rgba(255,255,255,0.18) !important;99 border-radius: 14px !important;100 font-family: Consolas, "Courier New", monospace !important;101 font-size: 0.95rem !important;102 line-height: 1.6 !important;103 padding: 1rem !important;104 }105 .stTextArea textarea::placeholder {106 color: #94a3b8 !important;107 opacity: 1 !important;108 }109 110 .stTextArea label {111 color: #e2e8f0 !important;112 font-weight: 700 !important;113 }114 115 /* Button */116 .stButton > button {117 background: linear-gradient(90deg, #ff4ecd, #7c5cff);118 color: white;119 border: none;120 border-radius: 12px;121 padding: 0.85rem 1.2rem;122 font-size: 1rem;123 font-weight: 700;124 width: 100%;125 box-shadow: 0 8px 20px rgba(124, 92, 255, 0.35);126 transition: 0.2s ease;127 }128 129 .stButton > button:hover {130 transform: translateY(-1px);131 opacity: 0.97;132 }133 134 /* Metrics */135 div[data-testid="stMetric"] {136 background: rgba(255, 255, 255, 0.05);137 border: 1px solid rgba(255,255,255,0.10);138 padding: 1rem;139 border-radius: 14px;140 }141 142 div[data-testid="stMetricLabel"] {143 color: #cbd5e1;144 font-weight: 600;145 }146 147 div[data-testid="stMetricValue"] {148 color: #ffffff;149 font-weight: 800;150 }151 152 /* Tabs */153 div[data-testid="stTabs"] {154 margin-top: 0.4rem;155 }156 157 button[data-baseweb="tab"] {158 color: #94a3b8 !important;159 font-weight: 600 !important;160 background: transparent !important;161 border: none !important;162 }163 164 button[data-baseweb="tab"]:hover {165 color: #ffffff !important;166 background: transparent !important;167 }168 169 button[data-baseweb="tab"][aria-selected="true"] {170 color: #ffffff !important;171 border-bottom: 2px solid #7c5cff !important;172 }173 174 /* Expander */175 div[data-testid="stExpander"] {176 background: rgba(255,255,255,0.04) !important;177 border: 1px solid rgba(255,255,255,0.08) !important;178 border-radius: 12px !important;179 overflow: hidden !important;180 }181 182 div[data-testid="stExpander"] summary {183 color: #e2e8f0 !important;184 font-weight: 600 !important;185 }186 187 div[data-testid="stExpander"] div[role="region"] {188 color: #dbe4f0 !important;189 }190 191 /* Inline code */192 code {193 background: rgba(255,255,255,0.08) !important;194 color: #f8fafc !important;195 padding: 2px 6px !important;196 border-radius: 6px !important;197 }198 199 /* Code block */200 pre {201 background: #020617 !important;202 color: #f8fafc !important;203 border-radius: 12px !important;204 }205 206 .stCodeBlock {207 border-radius: 14px;208 overflow: hidden;209 }210 211 hr {212 border: none;213 height: 1px;214 background: rgba(255,255,255,0.08);215 margin: 1.2rem 0 1.8rem 0;216 }217 /* REMOVE TOP WHITE BAR */218 header[data-testid="stHeader"] {219 background: transparent !important;220 }221 222 header {223 visibility: hidden;224 height: 0px;225 }226 227 div[data-testid="stDecoration"] {228 display: none !important;229 }230 231 232 .editor-label {233 color: #e2e8f0;234 font-weight: 700;235 font-size: 1.05rem;236 margin-bottom: 0.6rem;237 letter-spacing: 0.3px;238 }239 div[data-testid="stElementContainer"]:has(.streamlit_code-editor) {240 border: none;241 border-radius: 14px;242 overflow: hidden;243 background: transparent;244 padding: 0px;245 width: 100% !important;246 box-sizing: border-box !important;247 }248 249 div[data-testid="stElementContainer"]:has(.streamlit_code-editor) iframe {250 border: none !important;251 display: block;252 } 253 .metric-box {254 background: rgba(255,255,255,0.05);255 border: 1px solid rgba(255,255,255,0.08);256 border-radius: 14px;257 padding: 1rem;258 }259 260 .metric-label {261 color: #94a3b8;262 font-size: 0.85rem;263 font-weight: 600;264 margin-bottom: 0.4rem;265 }266 267 .metric-value {268 color: #ffffff;269 font-size: 1.6rem;270 font-weight: 800;271 }272</style>273""",274 unsafe_allow_html=True,275)276 277# ── Header ────────────────────────────────────────────────────────────────────278st.markdown(279 """280<div class="hero">281 <div class="main-title">🐍 Python Code Reviewer</div>282 <div class="subtitle">283 Paste your Python code and get a clean AI-powered review with bug detection,284 complexity analysis, design suggestions, and improved code.285 </div>286</div>287""",288 unsafe_allow_html=True,289)290 291# ── Input section ─────────────────────────────────────────────────────────────292st.markdown(293 '<div class="editor-label">Paste your Python code</div>', unsafe_allow_html=True294)295 296editor_result = code_editor(297 code="def my_function():\n # paste your Python code here\n pass",298 lang="python",299 theme="monokai",300 height=[14, 16],301 options={302 "wrap": True,303 "showLineNumbers": True,304 },305 response_mode=["debounce"],306 props={"style": {"borderRadius": "14px", "fontSize": "15px"}},307 component_props={308 "style": {309 "width": "100%",310 "maxWidth": "100%",311 "borderRadius": "14px",312 "overflow": "hidden",313 "background": "#07122b",314 "boxSizing": "border-box",315 "border": "1.5px solid rgba(255,255,255,1)",316 "boxShadow": "0 4px 24px rgba(0,0,0,0.3)",317 },318 "css": """319 font-weight: 500;320 border-radius: 14px;321 overflow: hidden;322 323 &.streamlit_code-editor {324 border-radius: 12px !important;325 overflow: hidden !important;326 }327 328 &.streamlit_code-editor .drag-handle,329 &.streamlit_code-editor [class*="drag"],330 &.streamlit_code-editor [class*="resize"],331 &.streamlit_code-editor [class*="handle"] {332 display: none !important;333 }334 335 &.streamlit_code-editor .ace-streamlit-dark.ace_editor {336 background-color: #07122b !important;337 color: #f8fafc !important;338 border-radius: 14px !important;339 overflow: hidden !important;340 }341 342 &.streamlit_code-editor .ace_gutter {343 background: #0b1633 !important;344 color: #94a3b8 !important;345 border-top-left-radius: 14px !important;346 border-bottom-left-radius: 14px !important;347 }348 349 &.streamlit_code-editor .ace_scroller {350 background-color: #07122b !important;351 }352 353 &.streamlit_code-editor .ace_content {354 background-color: #07122b !important;355 }356 357 &.streamlit_code-editor .ace_layer.ace_gutter-layer {358 background: #0b1633 !important;359 }360 361 &.streamlit_code-editor .ace_active-line {362 background: rgba(255,255,255,0.04) !important;363 }364 365 &.streamlit_code-editor .ace_cursor {366 color: #ffffff !important;367 }368 369 &.streamlit_code-editor .ace_identifier {370 color: #f8f8f2 !important;371 }372 373 &.streamlit_code-editor .ace_variable {374 color: #f8f8f2 !important;375 }376 377 &.streamlit_code-editor .ace_text-layer {378 color: #f8f8f2 !important;379 }380 381 &.streamlit_code-editor,382 &.streamlit_code-editor * {383 box-sizing: border-box !important;384 }385 386 &.streamlit_code-editor {387 border-radius: 12px !important;388 overflow: hidden !important;389 }390 """,391 "globalCSS": """392 :root {393 --streamlit-dark-background-color: #07122b;394 }395 """,396 },397 key="code_editor_main",398)399 400code_input = ""401if editor_result and isinstance(editor_result, dict):402 code_input = editor_result.get("text", "")403 404col1, col2 = st.columns([5.2, 1.3])405with col2:406 analyse_btn = st.button("🔍 Analyse My Code", key="analyse_main")407 408st.caption(409 f"Usage this session: {st.session_state.request_count}/{MAX_REQUESTS_PER_SESSION} • Cooldown: {COOLDOWN_SECONDS}s"410)411 412# ── Results ───────────────────────────────────────────────────────────────────413if analyse_btn:414 current_time = time.time()415 416 if not code_input.strip():417 st.warning("⚠️ Please paste some Python code first.")418 419 elif len(code_input) > MAX_CODE_LENGTH:420 st.warning(f"⚠️ Code is too long. Please keep it under {MAX_CODE_LENGTH} characters.")421 422 elif st.session_state.request_count >= MAX_REQUESTS_PER_SESSION:423 st.error(424 f"❌ Session limit reached. You can only analyse {MAX_REQUESTS_PER_SESSION} times per session."425 )426 427 elif current_time - st.session_state.last_request_time < COOLDOWN_SECONDS:428 remaining = int(COOLDOWN_SECONDS - (current_time - st.session_state.last_request_time))429 st.warning(f"⏳ Please wait {remaining} more seconds before trying again.")430 431 else:432 st.session_state.request_count += 1433 st.session_state.last_request_time = current_time434 435 with st.spinner("Reviewing your code..."):436 try:437 result = review_code(code_input)438 439 st.markdown("---")440 st.markdown(441 '<div class="section-heading">Review Summary</div>',442 unsafe_allow_html=True,443 )444 445 bugs_tab, complexity_tab, design_tab, rewritten_tab = st.tabs(446 ["Bugs", "Complexity", "Design Suggestions", "Rewritten Code"]447 )448 449 with bugs_tab:450 st.markdown('<div class="card">', unsafe_allow_html=True)451 st.markdown(452 '<div class="section-title bug-title">🐛 Bug Detection</div>',453 unsafe_allow_html=True,454 )455 456 bugs = result.get("bugs", [])457 if not bugs:458 st.success("✅ No bugs detected.")459 else:460 for bug in bugs:461 line = bug.get("line", "?")462 issue = bug.get("issue", "Issue found")463 fix = bug.get("fix", "No fix provided")464 465 with st.expander(f"Line {line} — {issue}"):466 st.markdown(f"**Fix:** {fix}")467 468 st.markdown("</div>", unsafe_allow_html=True)469 470 with complexity_tab:471 st.markdown('<div class="card">', unsafe_allow_html=True)472 st.markdown(473 '<div class="section-title complex-title">⏱️ Complexity Analysis</div>',474 unsafe_allow_html=True,475 )476 477 complexity = result.get("complexity", {})478 479 col_t, col_s = st.columns(2)480 481 with col_t:482 st.markdown(483 f"""484 <div class="metric-box">485 <div class="metric-label">Time Complexity</div>486 <div class="metric-value">{complexity.get("time", "N/A")}</div>487 </div>488 """,489 unsafe_allow_html=True,490 )491 492 with col_s:493 st.markdown(494 f"""495 <div class="metric-box">496 <div class="metric-label">Space Complexity</div>497 <div class="metric-value">{complexity.get("space", "N/A")}</div>498 </div>499 """,500 unsafe_allow_html=True,501 )502 503 st.markdown(504 f'<div class="helper-text">{complexity.get("explanation", "No explanation provided.")}</div>',505 unsafe_allow_html=True,506 )507 508 st.markdown("</div>", unsafe_allow_html=True)509 510 with design_tab:511 st.markdown('<div class="card">', unsafe_allow_html=True)512 st.markdown(513 '<div class="section-title oop-title">🏗️ Design Suggestions</div>',514 unsafe_allow_html=True,515 )516 517 oop_suggestions = result.get("oop_suggestions", [])518 if not oop_suggestions:519 st.success("✅ No design improvements needed.")520 else:521 for i, suggestion in enumerate(oop_suggestions, 1):522 st.markdown(523 f'<div class="helper-text"><b>{i}.</b> {suggestion}</div>',524 unsafe_allow_html=True,525 )526 527 st.markdown("</div>", unsafe_allow_html=True)528 529 with rewritten_tab:530 st.markdown('<div class="card">', unsafe_allow_html=True)531 st.markdown(532 '<div class="section-title rewrite-title">✨ Rewritten Code</div>',533 unsafe_allow_html=True,534 )535 536 rewritten = result.get("rewritten_code", "")537 if rewritten:538 st.code(rewritten, language="python")539 else:540 st.warning("No rewritten code provided.")541 542 st.markdown("</div>", unsafe_allow_html=True)543 544 except json.JSONDecodeError:545 st.error("❌ Could not parse the AI response. Please try again.")546 except Exception as e:547 st.error(f"❌ Something went wrong: {e}")548 