Spaces:
Build error
Build error
Update app.py
Browse files
app.py
CHANGED
|
@@ -3,7 +3,7 @@ import requests
|
|
| 3 |
import os
|
| 4 |
import base64
|
| 5 |
from urllib.parse import urlparse
|
| 6 |
-
|
| 7 |
|
| 8 |
|
| 9 |
# ββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -35,11 +35,11 @@ def fetch_repo_files(owner: str, repo: str, github_token: str | None = None) ->
|
|
| 35 |
|
| 36 |
tree = resp.json()
|
| 37 |
|
| 38 |
-
SKIP_DIRS
|
| 39 |
-
|
| 40 |
-
SKIP_EXTS
|
| 41 |
-
|
| 42 |
-
|
| 43 |
|
| 44 |
candidates = []
|
| 45 |
for item in tree.get("tree", []):
|
|
@@ -58,9 +58,9 @@ def fetch_repo_files(owner: str, repo: str, github_token: str | None = None) ->
|
|
| 58 |
# Prioritise: README first, then root-level, then shallow paths
|
| 59 |
def priority(p: str):
|
| 60 |
name = p.lower()
|
| 61 |
-
if "readme" in name:
|
| 62 |
-
if p.count("/") == 0:
|
| 63 |
-
if p.count("/") == 1:
|
| 64 |
return 3 + p.count("/")
|
| 65 |
|
| 66 |
selected = sorted(candidates, key=priority)[:18]
|
|
@@ -135,17 +135,17 @@ Order from most to least impactful.
|
|
| 135 |
"""
|
| 136 |
|
| 137 |
|
| 138 |
-
def analyze_repo(repo_url: str, github_token: str,
|
| 139 |
repo_url = repo_url or ""
|
| 140 |
github_token = github_token or ""
|
| 141 |
-
|
| 142 |
|
| 143 |
if not repo_url.strip():
|
| 144 |
return "β **Error:** Please enter a GitHub repository URL."
|
| 145 |
|
| 146 |
-
api_key = os.environ.get("
|
| 147 |
if not api_key:
|
| 148 |
-
return "β **Error:** Please enter your
|
| 149 |
|
| 150 |
try:
|
| 151 |
progress(0.10, desc="Parsing repository URLβ¦")
|
|
@@ -158,24 +158,29 @@ def analyze_repo(repo_url: str, github_token: str, groq_api_key: str, progress=g
|
|
| 158 |
if not file_contents:
|
| 159 |
return "β **Error:** No readable source files found. The repo may be empty or contain only binary files."
|
| 160 |
|
| 161 |
-
progress(0.65, desc="Running AI analysis with
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 171 |
)
|
| 172 |
|
| 173 |
progress(1.0, desc="Done!")
|
| 174 |
|
| 175 |
-
report = response.
|
| 176 |
header = (
|
| 177 |
f"## π Analysis Report β `{owner}/{repo}`\n"
|
| 178 |
-
f"*{len(file_contents)} files sampled Β· Powered by
|
| 179 |
)
|
| 180 |
return header + report
|
| 181 |
|
|
@@ -200,7 +205,7 @@ with gr.Blocks(title="GitHub Repo Analyzer") as demo:
|
|
| 200 |
gr.Markdown("# π GitHub Repo Analyzer", elem_id="title")
|
| 201 |
gr.Markdown(
|
| 202 |
"AI-powered **Code Quality & Documentation** analysis β paste any public repo and get a full report in seconds.\n\n"
|
| 203 |
-
"_Powered by **
|
| 204 |
elem_id="sub",
|
| 205 |
)
|
| 206 |
|
|
@@ -216,13 +221,13 @@ with gr.Blocks(title="GitHub Repo Analyzer") as demo:
|
|
| 216 |
|
| 217 |
with gr.Accordion("βοΈ API Keys", open=False):
|
| 218 |
gr.Markdown(
|
| 219 |
-
"π‘ _If the Space owner has set `
|
| 220 |
-
"Get a **free**
|
| 221 |
)
|
| 222 |
with gr.Row():
|
| 223 |
-
|
| 224 |
-
label="
|
| 225 |
-
placeholder="
|
| 226 |
type="password",
|
| 227 |
lines=1,
|
| 228 |
)
|
|
@@ -237,7 +242,7 @@ with gr.Blocks(title="GitHub Repo Analyzer") as demo:
|
|
| 237 |
|
| 238 |
analyze_btn.click(
|
| 239 |
fn=analyze_repo,
|
| 240 |
-
inputs=[repo_url_input, github_token_input,
|
| 241 |
outputs=output_md,
|
| 242 |
)
|
| 243 |
|
|
@@ -252,7 +257,7 @@ with gr.Blocks(title="GitHub Repo Analyzer") as demo:
|
|
| 252 |
)
|
| 253 |
|
| 254 |
gr.Markdown(
|
| 255 |
-
"---\nBuilt with **
|
| 256 |
"Made by [Worply](https://worply.tech)",
|
| 257 |
elem_id="footer",
|
| 258 |
)
|
|
|
|
| 3 |
import os
|
| 4 |
import base64
|
| 5 |
from urllib.parse import urlparse
|
| 6 |
+
import google.generativeai as genai
|
| 7 |
|
| 8 |
|
| 9 |
# ββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 35 |
|
| 36 |
tree = resp.json()
|
| 37 |
|
| 38 |
+
SKIP_DIRS = {"node_modules", ".git", "__pycache__", "venv", "env",
|
| 39 |
+
"dist", "build", ".next", "vendor", ".venv", "coverage"}
|
| 40 |
+
SKIP_EXTS = {".png", ".jpg", ".jpeg", ".gif", ".svg", ".ico", ".pdf",
|
| 41 |
+
".zip", ".woff", ".ttf", ".eot", ".mp4", ".mp3", ".lock",
|
| 42 |
+
".bin", ".exe", ".so", ".dylib"}
|
| 43 |
|
| 44 |
candidates = []
|
| 45 |
for item in tree.get("tree", []):
|
|
|
|
| 58 |
# Prioritise: README first, then root-level, then shallow paths
|
| 59 |
def priority(p: str):
|
| 60 |
name = p.lower()
|
| 61 |
+
if "readme" in name: return 0
|
| 62 |
+
if p.count("/") == 0: return 1
|
| 63 |
+
if p.count("/") == 1: return 2
|
| 64 |
return 3 + p.count("/")
|
| 65 |
|
| 66 |
selected = sorted(candidates, key=priority)[:18]
|
|
|
|
| 135 |
"""
|
| 136 |
|
| 137 |
|
| 138 |
+
def analyze_repo(repo_url: str, github_token: str, gemini_api_key: str, progress=gr.Progress()):
|
| 139 |
repo_url = repo_url or ""
|
| 140 |
github_token = github_token or ""
|
| 141 |
+
gemini_api_key = gemini_api_key or ""
|
| 142 |
|
| 143 |
if not repo_url.strip():
|
| 144 |
return "β **Error:** Please enter a GitHub repository URL."
|
| 145 |
|
| 146 |
+
api_key = os.environ.get("GEMINI_API_KEY") or gemini_api_key.strip()
|
| 147 |
if not api_key:
|
| 148 |
+
return "β **Error:** Please enter your Gemini API key (free at [aistudio.google.com](https://aistudio.google.com))."
|
| 149 |
|
| 150 |
try:
|
| 151 |
progress(0.10, desc="Parsing repository URLβ¦")
|
|
|
|
| 158 |
if not file_contents:
|
| 159 |
return "β **Error:** No readable source files found. The repo may be empty or contain only binary files."
|
| 160 |
|
| 161 |
+
progress(0.65, desc="Running AI analysis with Gemini 2.0 Flashβ¦")
|
| 162 |
+
|
| 163 |
+
# Configure Gemini
|
| 164 |
+
genai.configure(api_key=api_key)
|
| 165 |
+
model = genai.GenerativeModel(
|
| 166 |
+
model_name="gemini-2.0-flash",
|
| 167 |
+
system_instruction=SYSTEM_PROMPT
|
| 168 |
+
)
|
| 169 |
+
|
| 170 |
+
response = model.generate_content(
|
| 171 |
+
build_analysis_prompt(owner, repo, file_contents),
|
| 172 |
+
generation_config=genai.GenerationConfig(
|
| 173 |
+
max_output_tokens=2048,
|
| 174 |
+
temperature=0.3,
|
| 175 |
+
)
|
| 176 |
)
|
| 177 |
|
| 178 |
progress(1.0, desc="Done!")
|
| 179 |
|
| 180 |
+
report = response.text
|
| 181 |
header = (
|
| 182 |
f"## π Analysis Report β `{owner}/{repo}`\n"
|
| 183 |
+
f"*{len(file_contents)} files sampled Β· Powered by Gemini 2.0 Flash*\n\n---\n\n"
|
| 184 |
)
|
| 185 |
return header + report
|
| 186 |
|
|
|
|
| 205 |
gr.Markdown("# π GitHub Repo Analyzer", elem_id="title")
|
| 206 |
gr.Markdown(
|
| 207 |
"AI-powered **Code Quality & Documentation** analysis β paste any public repo and get a full report in seconds.\n\n"
|
| 208 |
+
"_Powered by **Gemini 2.0 Flash** β blazing fast & free._",
|
| 209 |
elem_id="sub",
|
| 210 |
)
|
| 211 |
|
|
|
|
| 221 |
|
| 222 |
with gr.Accordion("βοΈ API Keys", open=False):
|
| 223 |
gr.Markdown(
|
| 224 |
+
"π‘ _If the Space owner has set `GEMINI_API_KEY` as a HF Secret, you don't need to fill this in._\n\n"
|
| 225 |
+
"Get a **free** Gemini API key at [aistudio.google.com](https://aistudio.google.com) β no credit card needed."
|
| 226 |
)
|
| 227 |
with gr.Row():
|
| 228 |
+
gemini_key_input = gr.Textbox(
|
| 229 |
+
label="Gemini API Key (free)",
|
| 230 |
+
placeholder="AIza_xxxxxxxxxxxx",
|
| 231 |
type="password",
|
| 232 |
lines=1,
|
| 233 |
)
|
|
|
|
| 242 |
|
| 243 |
analyze_btn.click(
|
| 244 |
fn=analyze_repo,
|
| 245 |
+
inputs=[repo_url_input, github_token_input, gemini_key_input],
|
| 246 |
outputs=output_md,
|
| 247 |
)
|
| 248 |
|
|
|
|
| 257 |
)
|
| 258 |
|
| 259 |
gr.Markdown(
|
| 260 |
+
"---\nBuilt with **Gemini 2.0 Flash** Β· [Get your free key](https://aistudio.google.com) Β· "
|
| 261 |
"Made by [Worply](https://worply.tech)",
|
| 262 |
elem_id="footer",
|
| 263 |
)
|