Spaces:
Runtime error
Runtime error
| import streamlit as st | |
| import requests | |
| import json | |
| import os | |
| from datetime import datetime | |
| # ββ Page config βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| st.set_page_config( | |
| page_title="AI Code Reviewer", | |
| page_icon="π", | |
| layout="wide", | |
| ) | |
| # ββ Styles ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| st.markdown(""" | |
| <style> | |
| /* Tighten main padding */ | |
| .block-container { padding-top: 2rem; padding-bottom: 2rem; } | |
| /* Score badge */ | |
| .score-badge { | |
| display: inline-block; | |
| font-size: 2.5rem; | |
| font-weight: 700; | |
| padding: 0.4rem 1.2rem; | |
| border-radius: 12px; | |
| margin-bottom: 0.5rem; | |
| } | |
| .score-high { background: #d4edda; color: #1a6630; } | |
| .score-mid { background: #fff3cd; color: #7d5a00; } | |
| .score-low { background: #f8d7da; color: #7a1c1c; } | |
| /* Bug card */ | |
| .bug-card { | |
| background: #fff8f5; | |
| border-left: 4px solid #d85a30; | |
| border-radius: 6px; | |
| padding: 0.75rem 1rem; | |
| margin-bottom: 0.6rem; | |
| } | |
| .bug-card .line-tag { | |
| font-size: 0.75rem; | |
| font-weight: 600; | |
| color: #993c1d; | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| } | |
| .bug-card .fix { | |
| font-size: 0.85rem; | |
| color: #555; | |
| margin-top: 0.3rem; | |
| } | |
| /* Suggestion pill */ | |
| .suggestion-pill { | |
| display: inline-block; | |
| background: #eeedfe; | |
| color: #3c3489; | |
| border-radius: 999px; | |
| padding: 0.3rem 0.9rem; | |
| font-size: 0.85rem; | |
| margin: 0.2rem 0.2rem 0.2rem 0; | |
| } | |
| /* Divider */ | |
| hr { border-color: #eee; } | |
| </style> | |
| """, unsafe_allow_html=True) | |
| # ββ Config ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| N8N_WEBHOOK_URL = os.environ.get( | |
| "N8N_WEBHOOK_URL", | |
| "https://YOUR-N8N-INSTANCE/webhook/code-review" # β replace or set env var | |
| ) | |
| LANGUAGES = [ | |
| "Python", "JavaScript", "TypeScript", "Java", "C", "C++", | |
| "C#", "Go", "Rust", "Ruby", "PHP", "Swift", "Kotlin", "SQL", "Other" | |
| ] | |
| # ββ Helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def score_class(score: int) -> str: | |
| if score >= 8: | |
| return "score-high" | |
| if score >= 5: | |
| return "score-mid" | |
| return "score-low" | |
| def call_n8n(code: str, language: str) -> dict: | |
| """POST to n8n webhook and return parsed review dict.""" | |
| payload = { | |
| "code": code, | |
| "language": language, | |
| "timestamp": datetime.utcnow().isoformat(), | |
| } | |
| try: | |
| resp = requests.post(N8N_WEBHOOK_URL, json=payload, timeout=60) | |
| resp.raise_for_status() | |
| return resp.json() | |
| except requests.exceptions.Timeout: | |
| return {"error": "Request timed out β n8n or DeepSeek took too long."} | |
| except requests.exceptions.ConnectionError: | |
| return {"error": "Could not reach n8n. Is the webhook URL correct and the workflow active?"} | |
| except requests.exceptions.HTTPError as e: | |
| return {"error": f"n8n returned HTTP {e.response.status_code}: {e.response.text[:300]}"} | |
| except json.JSONDecodeError: | |
| return {"error": "n8n returned a non-JSON response. Check the Respond to Webhook node."} | |
| def render_review(review: dict): | |
| """Render the structured review returned by DeepSeek.""" | |
| # ββ Score ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| score = review.get("score", 0) | |
| cls = score_class(score) | |
| st.markdown( | |
| f'<span class="score-badge {cls}">{score}/10</span>', | |
| unsafe_allow_html=True | |
| ) | |
| # ββ Summary ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| summary = review.get("summary", "") | |
| if summary: | |
| st.markdown(f"**Summary:** {summary}") | |
| st.markdown("---") | |
| col_bugs, col_sugg = st.columns([1, 1], gap="large") | |
| # ββ Bugs βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with col_bugs: | |
| bugs = review.get("bugs", []) | |
| st.markdown(f"#### π Issues found `{len(bugs)}`") | |
| if not bugs: | |
| st.success("No bugs detected.") | |
| for bug in bugs: | |
| line = bug.get("line", "?") | |
| issue = bug.get("issue", "") | |
| fix = bug.get("fix", "") | |
| st.markdown( | |
| f"""<div class="bug-card"> | |
| <div class="line-tag">Line {line}</div> | |
| <div>{issue}</div> | |
| <div class="fix">π‘ {fix}</div> | |
| </div>""", | |
| unsafe_allow_html=True | |
| ) | |
| # ββ Suggestions ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with col_sugg: | |
| suggestions = review.get("suggestions", []) | |
| st.markdown(f"#### β¨ Suggestions `{len(suggestions)}`") | |
| if not suggestions: | |
| st.info("No further suggestions.") | |
| for s in suggestions: | |
| st.markdown( | |
| f'<span class="suggestion-pill">{s}</span>', | |
| unsafe_allow_html=True | |
| ) | |
| # ββ Raw JSON expander ββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with st.expander("Raw JSON response"): | |
| st.json(review) | |
| # ββ Layout ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| st.title("π AI Code Reviewer") | |
| st.caption("Paste your code β get structured feedback powered by DeepSeek via n8n") | |
| left, right = st.columns([1, 1], gap="large") | |
| with left: | |
| language = st.selectbox("Language", LANGUAGES) | |
| code = st.text_area( | |
| "Paste your code here", | |
| height=380, | |
| placeholder="def fibonacci(n):\n if n <= 0:\n return []\n ...", | |
| ) | |
| submit = st.button("Review my code β", type="primary", use_container_width=True) | |
| with right: | |
| st.markdown("#### Review") | |
| if submit: | |
| if not code.strip(): | |
| st.warning("Please paste some code first.") | |
| else: | |
| with st.spinner("Sending to n8n β DeepSeekβ¦"): | |
| result = call_n8n(code.strip(), language) | |
| if "error" in result: | |
| st.error(result["error"]) | |
| else: | |
| render_review(result) | |
| else: | |
| st.markdown( | |
| """ | |
| <div style="color:#aaa; margin-top:4rem; text-align:center;"> | |
| Your review will appear here.<br><br> | |
| <span style="font-size:2.5rem;">π€</span> | |
| </div> | |
| """, | |
| unsafe_allow_html=True | |
| ) | |
| # ββ Footer ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| st.markdown("---") | |
| st.caption("Built with Streamlit Β· n8n Β· DeepSeek β logs saved to Google Sheets") | |