viraja1's picture
Update app.py
cb79caa verified
Raw
History Blame Contribute Delete
12.1 kB
import json
import gradio as gr
import requests
import base64
import markdown
GITHUB = "https://api.github.com"
# ---------------------------------------------------------
# MCP RESOURCES (HTML Renderers)
# ---------------------------------------------------------
@gr.mcp.resource("ui://repo_info.html", mime_type="text/html+skybridge")
def repo_info_ui():
return """
<div id="repo"></div>
<script>
function render() {
let d;
try {
// Parse the JSON string
d = JSON.parse(window.openai?.toolOutput?.text || '{}');
} catch (e) {
return;
}
if (!d.full_name){
document.getElementById("repo").innerHTML = "<p>No repository info found.</p>";
return;
}
document.getElementById("repo").innerHTML = `
<h2>${d.full_name}</h2>
<p>${d.description || "No description"}</p>
<div style="display:flex;gap:1rem;margin-top:1rem;font-size:15px">
<span>⭐ ${d.stars}</span>
<span>🍴 ${d.forks}</span>
<span>❗ ${d.open_issues}</span>
</div>
<p style="margin-top:1rem;">
<a href="${d.html_url}" target="_blank">Open on GitHub</a>
</p>
`;
}
render();
window.addEventListener("openai:set_globals", render);
</script>
"""
@gr.mcp.resource("ui://issues.html", mime_type="text/html+skybridge")
def issues_ui():
return """
<div id="issues"></div>
<script>
function render() {
let issues;
try {
// Parse the JSON string
issues = JSON.parse(window.openai?.toolOutput?.text || '[]');
} catch (e) {
return;
}
let html = "<h3>Issues</h3><ul>";
for (const i of issues) {
html += `<li><a href="${i.url}" target="_blank">#${i.number} — ${i.title}</a></li>`;
}
html += "</ul>";
document.getElementById("issues").innerHTML = html;
}
render();
window.addEventListener("openai:set_globals", (event) => {
if (event.detail?.globals?.toolInput) {
render();
}
}, { passive: true });
</script>
"""
@gr.mcp.resource("ui://pulls.html", mime_type="text/html+skybridge")
def pulls_ui():
return """
<div id="pulls"></div>
<script>
function render() {
const toolOutput = window.openai?.toolOutput;
console.log("toolOutput:", window.openai?.toolOutput);
if (!toolOutput || !toolOutput.text) {
document.getElementById("pulls").innerHTML = "<p>No PRs found.</p>";
return;
}
const jsonString = toolOutput.text
let prs = [];
try {
prs = JSON.parse(jsonString);
} catch (e) {
console.error("Failed to parse PR list:", e);
}
let html = "<h3>Pull Requests</h3><ul>";
for (const p of prs) {
html += `<li><a href="${p.url}" target="_blank">#${p.number} — ${p.title}</a></li>`;
}
html += "</ul>";
document.getElementById("pulls").innerHTML = html;
}
render();
window.addEventListener("openai:set_globals", (event) => {
console.log("Received openai:set_globals event:", event);
render();
});
</script>
"""
@gr.mcp.resource("ui://readme.html", mime_type="text/html+skybridge")
def readme_ui():
return """
<div id="readme"></div>
<script>
function render() {
d = JSON.parse(window.openai?.toolOutput?.text || '{}');
if(d?.html === undefined) {
document.getElementById("readme").innerHTML = "<p>No README found.</p>";
return;
}
const html = d.html;
document.getElementById("readme").innerHTML = `<h2>README</h2><div style="padding:1rem;background:#fafafa;border:1px solid #ccc">${html}</div>`;
}
render();
window.addEventListener("openai:set_globals", render);
</script>
"""
@gr.mcp.resource("ui://contributors.html", mime_type="text/html+skybridge")
def contributors_ui():
return """
<div id="contributors"></div>
<script>
function render() {
let list;
try {
// Parse the JSON string
list = JSON.parse(window.openai?.toolOutput?.text || '[]');
} catch (e) {
return;
}
let html = "<h3>Top Contributors</h3><ul>";
for (const c of list) {
html += `<li><img src="${c.avatar}" width="24" style="border-radius:50%;vertical-align:middle;margin-right:6px"><a href="${c.url}" target="_blank">${c.name}</a> — ${c.commits} commits</li>`;
}
html += "</ul>";
document.getElementById("contributors").innerHTML = html;
}
render();
window.addEventListener("openai:set_globals", render);
</script>
"""
@gr.mcp.resource("ui://search.html", mime_type="text/html+skybridge")
def search_ui():
return """
<div id="repos"></div>
<script>
function render() {
let list;
try {
// Parse the JSON string
list = JSON.parse(window.openai?.toolOutput?.text || '[]');
} catch (e) {
return;
}
let html = "<h3>Search Results</h3><ul>";
for (const r of list) {
html += `<li><a href="${r.url}" target="_blank">${r.full_name}</a> — ⭐ ${r.stars}<br/><small>${r.description || ""}</small></li>`;
}
html += "</ul>";
document.getElementById("repos").innerHTML = html;
}
render();
window.addEventListener("openai:set_globals", render);
</script>
"""
@gr.mcp.resource("ui://files.html", mime_type="text/html+skybridge")
def files_ui():
return """
<div id="files"></div>
<script>
function render() {
let items;
try {
// Parse the JSON string
items = JSON.parse(window.openai?.toolOutput?.text || '[]');
} catch (e) {
return;
}
let html = "<h3>Files</h3><ul>";
for (const f of items) {
html += `<li>${f.type === "dir" ? "📁" : "📄"} <a href="${f.url}" target="_blank">${f.path}</a></li>`;
}
html += "</ul>";
document.getElementById("files").innerHTML = html;
}
render();
window.addEventListener("openai:set_globals", render);
</script>
"""
# ---------------------------------------------------------
# HELPERS
# ---------------------------------------------------------
def gget(url, params=None):
r = requests.get(url, params=params, headers={"Accept": "application/vnd.github+json"})
if r.status_code != 200:
raise Exception(f"GitHub API error ({r.status_code}): {r.text}")
return r.json()
# ---------------------------------------------------------
# MCP TOOLS (with widget metadata)
# ---------------------------------------------------------
def tool_meta(resource_ui):
"""Helper to generate the full _meta dict for tools"""
return {
"openai/outputTemplate": resource_ui,
"openai/resultCanProduceWidget": True,
"openai/widgetAccessible": True
}
@gr.mcp.tool(_meta=tool_meta("ui://repo_info.html"))
def get_repo_info(full_name: str) -> str:
d = gget(f"{GITHUB}/repos/{full_name}")
info = {
"full_name": d["full_name"],
"description": d.get("description", ""),
"stars": d["stargazers_count"],
"forks": d["forks_count"],
"open_issues": d["open_issues_count"],
"html_url": d["html_url"]
}
return json.dumps(info)
@gr.mcp.tool(_meta=tool_meta("ui://issues.html"))
def list_issues(full_name: str, per_page: int = 10) -> str:
issues = gget(f"{GITHUB}/repos/{full_name}/issues", {"per_page": per_page})
issues = [{"title": i["title"], "number": i["number"], "url": i["html_url"]} for i in issues]
return json.dumps(issues)
@gr.mcp.tool(_meta=tool_meta("ui://pulls.html"))
def list_pull_requests(full_name: str, per_page: int = 10) -> str:
prs = gget(f"{GITHUB}/repos/{full_name}/pulls", {"per_page": per_page})
prs = [{"title": p["title"], "number": p["number"], "url": p["html_url"]} for p in prs]
return json.dumps(prs)
@gr.mcp.tool(_meta=tool_meta("ui://readme.html"))
def get_readme(full_name: str) -> str:
d = gget(f"{GITHUB}/repos/{full_name}/readme")
content = base64.b64decode(d["content"]).decode()
html = markdown.markdown(content)
data = {"html": html}
return json.dumps(data)
@gr.mcp.tool(_meta=tool_meta("ui://contributors.html"))
def list_contributors(full_name: str, limit: int = 10) -> str:
c = gget(f"{GITHUB}/repos/{full_name}/contributors")
c = [{
"name": i["login"],
"avatar": i["avatar_url"],
"url": i["html_url"],
"commits": i["contributions"]
} for i in c[:limit]]
return json.dumps(c)
@gr.mcp.tool(_meta=tool_meta("ui://search.html"))
def search_repos(query: str, language: str = None, per_page: int = 10) -> str:
params = {"q": query, "per_page": per_page, "sort": "stars"}
if language:
params["q"] += f"+language:{language}"
d = gget(f"{GITHUB}/search/repositories", params)
d = [{"full_name": r["full_name"], "stars": r["stargazers_count"], "description": r["description"], "url": r["html_url"]} for r in d["items"]]
return json.dumps(d)
@gr.mcp.tool(_meta=tool_meta("ui://files.html"))
def list_files(full_name: str) -> str:
d = gget(f"{GITHUB}/repos/{full_name}/git/trees/HEAD?recursive=1")
d = [{"path": i["path"], "type": i["type"], "url": f"https://github.com/{full_name}/blob/HEAD/{i['path']}"} for i in d["tree"]]
return json.dumps(d)
# ---------------------------------------------------------
# Gradio UI
# ---------------------------------------------------------
with gr.Blocks() as demo:
gr.Markdown("### GitHub Helper Bot — use inside ChatGPT")
repo_input = gr.Textbox(label="Repository Full Name (owner/repo)")
btn_repo = gr.Button("Get Repo Info")
btn_issues = gr.Button("List Issues")
btn_prs = gr.Button("List Pull Requests")
btn_readme = gr.Button("Get README")
btn_contrib = gr.Button("List Contributors")
btn_files = gr.Button("List Files")
search_query = gr.Textbox(label="Search Repositories")
btn_search = gr.Button("Search Repos")
tool_response = gr.TextArea(label="Tool Response")
html_out = gr.TextArea(label="HTML Output")
# Wire tools + resources
btn_repo.click(get_repo_info, inputs=repo_input, outputs=tool_response)
btn_repo.click(repo_info_ui, outputs=html_out)
btn_issues.click(list_issues, inputs=repo_input, outputs=tool_response)
btn_issues.click(issues_ui, outputs=html_out)
btn_prs.click(list_pull_requests, inputs=repo_input, outputs=tool_response)
btn_prs.click(pulls_ui, outputs=html_out)
btn_readme.click(get_readme, inputs=repo_input, outputs=tool_response)
btn_readme.click(readme_ui, outputs=html_out)
btn_contrib.click(list_contributors, inputs=repo_input, outputs=tool_response)
btn_contrib.click(contributors_ui, outputs=html_out)
btn_files.click(list_files, inputs=repo_input, outputs=tool_response)
btn_files.click(files_ui, outputs=html_out)
btn_search.click(search_repos, inputs=search_query, outputs=tool_response)
btn_search.click(search_ui, outputs=html_out)
if __name__ == "__main__":
demo.launch(share=True, mcp_server=True)