aika42's picture
Upload 3 files
350f63e verified
Raw
History Blame Contribute Delete
8.11 kB
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 &nbsp; `{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 &nbsp; `{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")