File size: 5,472 Bytes
5acee3f 79ceb0f d7c6008 79ceb0f d7c6008 79ceb0f d7c6008 5acee3f 79ceb0f 5acee3f 79ceb0f d7c6008 79ceb0f d7c6008 79ceb0f d7c6008 79ceb0f d7c6008 79ceb0f d7c6008 79ceb0f d7c6008 79ceb0f 5acee3f 79ceb0f d7c6008 5acee3f 79ceb0f 5acee3f 79ceb0f d7c6008 79ceb0f 5acee3f 79ceb0f d7c6008 5acee3f fcc65c4 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
import os, shutil, tempfile, re
from pathlib import Path
import gradio as gr
from git import Repo
import requests
# ---------------- CONFIG ----------------
OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
OPENROUTER_MODEL = "nvidia/nemotron-nano-12b-v2-vl:free"
OPENROUTER_URL = "https://openrouter.ai/api/v1/chat/completions"
HEADERS = {
"Authorization": f"Bearer {OPENROUTER_API_KEY}",
"Content-Type": "application/json",
}
ALLOWED_EXT = {
".py", ".ipynb", ".md", ".txt", ".js", ".ts", ".tsx", ".jsx", ".java",
".kt", ".c", ".cpp", ".cs", ".go", ".rs", ".rb", ".php", ".sql", ".html",
".css", ".yml", ".yaml", ".toml", ".ini", ".json"
}
SKIP_DIRS = {
"node_modules", ".git", "dist", "build", "out", "venv", ".venv",
"__pycache__", ".next", ".cache", "target", "bin", "obj", ".idea", ".vscode"
}
MAX_FILE_BYTES = 800_000
# ---------------- REPO UTILITIES ----------------
def clone_repo(url: str) -> Path:
d = Path(tempfile.mkdtemp(prefix=".tmp_repo_")).resolve()
Repo.clone_from(url, d, depth=1)
return d
def read_repo_text(repo_dir: Path) -> str:
buf = []
for root, dirs, files in os.walk(repo_dir):
dirs[:] = [x for x in dirs if x not in SKIP_DIRS]
for f in files:
p = Path(root) / f
if p.suffix.lower() in ALLOWED_EXT and p.stat().st_size <= MAX_FILE_BYTES:
try:
txt = p.read_text(encoding="utf-8", errors="ignore")
if txt.strip():
rel = str(p.relative_to(repo_dir))
buf.append(f"\n=== FILE: {rel} ===\n{txt}")
except Exception:
pass
return "\n".join(buf)
def analyze_repo(url: str):
if not url or not re.match(r"^https?://", url.strip()):
return None, "β Invalid URL"
repo_dir = None
try:
repo_dir = clone_repo(url.strip())
text = read_repo_text(repo_dir)
if not text.strip():
return None, "β οΈ No readable text files found"
kb_size = len(text) // 1000
return text, f"β
Repo loaded successfully ({kb_size} KB of text)"
except Exception as e:
return None, f"β Error: {e}"
finally:
if repo_dir and Path(repo_dir).exists():
shutil.rmtree(repo_dir, ignore_errors=True)
# ---------------- OPENROUTER CLIENT ----------------
def openrouter_chat(system_prompt, user_prompt, context=""):
messages = [{"role": "system", "content": system_prompt}]
if context:
messages.append({"role": "system", "content": f"Repository context:\n{context}"})
messages.append({"role": "user", "content": user_prompt})
payload = {"model": OPENROUTER_MODEL, "messages": messages}
try:
r = requests.post(OPENROUTER_URL, headers=HEADERS, json=payload, timeout=120)
r.raise_for_status()
obj = r.json()
if "choices" in obj and obj["choices"]:
msg = obj["choices"][0]["message"]["content"]
return msg.strip()
return "[OpenRouter] Unexpected response format."
except Exception as e:
return f"[OpenRouter error] {e}"
# ---------------- CHAT LOGIC ----------------
SYSTEM_PROMPT = (
"You are an expert developer assistant. You help users explore and understand "
"a GitHub repository. Base every response strictly on the repo's content and structure. "
"If unsure, say so. Explain clearly and concisely. Avoid hallucinating."
)
def chat_repo(user_msg, chat_history, repo_text):
if not repo_text:
chat_history.append({"role": "assistant", "content": "β Please analyze a repository first."})
return chat_history, ""
context = repo_text[:120000] # truncate for token safety
response = openrouter_chat(SYSTEM_PROMPT, user_msg, context)
chat_history.append({"role": "user", "content": user_msg})
chat_history.append({"role": "assistant", "content": response})
return chat_history, ""
# ---------------- GRADIO UI ----------------
with gr.Blocks(title="Repo Chatbot Β· OpenRouter") as demo:
gr.Markdown(
"""
# π€ Repo Chatbot β powered by OpenRouter
Chat with your GitHub repository!
Upload a repo URL and ask anything about its **code, structure, or design**.
_(No embeddings, just pure context reasoning.)_
"""
)
repo_state = gr.State()
chat_history = gr.State([])
with gr.Row():
repo_url = gr.Textbox(
label="GitHub repo URL",
placeholder="https://github.com/owner/repo",
scale=4
)
analyze_btn = gr.Button("π Analyze Repo", scale=1)
status_box = gr.Markdown()
# β
Add type='messages' to match new format
chatbot = gr.Chatbot(label="Repo Chatbot", height=500, type="messages")
user_box = gr.Textbox(label="Ask something about the repo...")
clear_btn = gr.Button("π§Ή Clear Chat")
# ---- CALLBACKS ----
def analyze_repo_cb(url):
text, status = analyze_repo(url)
return text, status
analyze_btn.click(analyze_repo_cb, inputs=[repo_url], outputs=[repo_state, status_box])
user_box.submit(chat_repo, inputs=[user_box, chat_history, repo_state],
outputs=[chatbot, user_box])
clear_btn.click(lambda: ([], ""), None, [chatbot, user_box])
demo.queue() # β
removed unsupported arg
demo.launch(server_name="0.0.0.0", server_port=7860,mcp_server=True)
|