Resham2987 commited on
Commit
460b37e
Β·
verified Β·
1 Parent(s): f5881dc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +463 -47
app.py CHANGED
@@ -1,3 +1,437 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
  import re
3
  import requests
@@ -68,7 +502,6 @@ LOADER_HTML = """
68
  </style>
69
  """
70
 
71
-
72
  # ─────────────────────────────────────────
73
  # GitHub helpers
74
  # ─────────────────────────────────────────
@@ -138,13 +571,12 @@ def build_folder_tree(paths, max_depth=3):
138
  render(tree)
139
  return "\n".join(lines)
140
 
141
-
142
  # ─────────────────────────────────────────
143
  # OpenRouter
144
  # ─────────────────────────────────────────
145
  def call_openrouter(prompt: str) -> str:
146
  if not OPENROUTER_API_KEY:
147
- return "ERROR: OPENROUTER_API_KEY secret not set in HF Space settings."
148
  r = requests.post(
149
  "https://openrouter.ai/api/v1/chat/completions",
150
  headers={"Authorization": f"Bearer {OPENROUTER_API_KEY}",
@@ -165,32 +597,36 @@ def call_openrouter(prompt: str) -> str:
165
  return obj["choices"][0]["message"]["content"].strip()
166
  return "[No response from model]"
167
 
168
-
169
  # ─────────────────────────────────────────
170
- # Main generator β€” plain function (no yield)
171
- # Returns: (loader_update, status_str, readme_str)
172
  # ─────────────────────────────────────────
173
  def generate_readme(repo_url, license_choice, extra_context, sections):
174
- hide = gr.update(visible=False)
175
 
176
  if not repo_url.strip():
177
- return hide, "Please paste a GitHub repository URL.", ""
 
178
 
179
  owner, repo = parse_repo_url(repo_url)
180
  if not owner:
181
- return hide, "Could not parse GitHub URL. Use: https://github.com/owner/repo", ""
 
182
 
183
  log = []
184
- log.append(f"Fetching {owner}/{repo} from GitHub...")
 
 
185
 
186
  try:
187
  meta = fetch_repo_meta(owner, repo)
188
  languages = fetch_languages(owner, repo)
189
  all_files = fetch_tree(owner, repo, meta.get("default_branch", "HEAD"))
190
  except Exception as e:
191
- return hide, "\n".join(log) + f"\nGitHub API error: {e}", ""
 
192
 
193
- log.append(f"Got {len(all_files)} files. Languages: {', '.join(languages) or 'unknown'}")
 
194
 
195
  key_files = ["requirements.txt", "pyproject.toml", "package.json",
196
  "Cargo.toml", "setup.py", "Dockerfile", ".env.example", "Makefile"]
@@ -236,15 +672,17 @@ Use **{license_choice}** license in the License section.
236
 
237
  Now generate the full README.md:"""
238
 
239
- log.append("Calling AI model... (this may take 15-30s)")
 
240
 
241
  try:
242
  readme = call_openrouter(prompt)
243
  except Exception as e:
244
- return hide, "\n".join(log) + f"\nAI error: {e}", ""
 
245
 
246
- log.append("Done! Your README is ready below.")
247
- return hide, "\n".join(log), readme
248
 
249
 
250
  # ─────────────────────────────────────────
@@ -318,29 +756,14 @@ textarea:focus, input[type="text"]:focus {
318
  color:#e0e8ff !important;
319
  line-height:1.55 !important;
320
  }
321
- .divider {
322
- height:1px;
323
- background:linear-gradient(90deg,transparent,var(--rim),transparent);
324
- margin:6px 0 20px;
325
- }
326
  .tip {
327
- background:var(--card);
328
- border:1px solid var(--rim);
329
- border-left:3px solid var(--gold);
330
- border-radius:6px;
331
- padding:11px 15px;
332
- font-size:.78rem;
333
- color:var(--dim);
334
- line-height:1.65;
335
- margin-bottom:16px;
336
- font-family:'Fira Code',monospace;
337
- }
338
- .ftr {
339
- text-align:center; margin-top:36px;
340
- color:#1e2535; font-size:.68rem;
341
- font-family:'Fira Code',monospace;
342
- letter-spacing:.5px;
343
  }
 
344
  """
345
 
346
  # ─────────────────────────────────────────
@@ -361,7 +784,6 @@ with gr.Blocks(title="README Forge", css=CSS,
361
 
362
  with gr.Row(equal_height=False):
363
 
364
- # LEFT panel
365
  with gr.Column(scale=1, min_width=320):
366
  gr.HTML('<div class="lbl">Configuration</div><div class="divider"></div>')
367
  gr.HTML('<div class="tip">Paste any public GitHub URL. Real file trees, languages and metadata are fetched automatically.</div>')
@@ -389,10 +811,9 @@ with gr.Blocks(title="README Forge", css=CSS,
389
  interactive=True,
390
  )
391
  with gr.Row():
392
- gen_btn = gr.Button("Generate README", variant="primary", size="lg")
393
- clear_btn = gr.Button("Clear", variant="secondary")
394
 
395
- # RIGHT panel
396
  with gr.Column(scale=2):
397
 
398
  loader = gr.HTML(value="", visible=False)
@@ -411,14 +832,9 @@ with gr.Blocks(title="README Forge", css=CSS,
411
  elem_classes=["out-box"],
412
  )
413
 
414
- gr.HTML('<div class="ftr">README Forge &nbsp;&middot;&nbsp; Powered by OpenRouter &middot; nvidia/nemotron-nano-12b &middot; GitHub API</div>')
415
 
416
- # Show loader β†’ run generation (which returns loader=hidden) β†’ done
417
  gen_btn.click(
418
- fn=lambda: gr.update(value=LOADER_HTML, visible=True),
419
- inputs=None,
420
- outputs=loader,
421
- ).then(
422
  fn=generate_readme,
423
  inputs=[repo_url, license_choice, extra_context, sections],
424
  outputs=[loader, status_box, output_box],
 
1
+ # import os
2
+ # import re
3
+ # import requests
4
+ # import gradio as gr
5
+
6
+ # # ─────────────────────────────────────────
7
+ # # Config
8
+ # # ─────────────────────────────────────────
9
+ # OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY")
10
+ # GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", "")
11
+ # OPENROUTER_MODEL = "nvidia/nemotron-nano-12b-v2-vl:free"
12
+
13
+ # SYSTEM_PROMPT = """You are an elite open-source developer and technical writer.
14
+ # Generate a stunning, COMPLETE GitHub README.md strictly in Markdown format.
15
+
16
+ # Rules:
17
+ # - Use real markdown: headers, code blocks, badges, tables, emojis
18
+ # - Be SPECIFIC β€” use the actual project name, language, file names, and real details from context
19
+ # - Include EVERY section listed by the user β€” do NOT skip any
20
+ # - Folder structure must be a real ASCII tree using the actual files/dirs provided
21
+ # - Contributing section must be detailed with step-by-step git workflow
22
+ # - Badge line must use shields.io markdown syntax (realistic placeholders)
23
+ # - Output ONLY the markdown. No explanations, no preamble, no trailing comments."""
24
+
25
+ # ALL_SECTIONS = [
26
+ # "Project Title & Tagline",
27
+ # "Badges (stars, forks, license, language)",
28
+ # "Table of Contents",
29
+ # "About / Overview",
30
+ # "Features",
31
+ # "Tech Stack",
32
+ # "Folder / Project Structure",
33
+ # "Prerequisites",
34
+ # "Installation & Setup",
35
+ # "Usage / Examples",
36
+ # "Environment Variables",
37
+ # "Roadmap",
38
+ # "Contributing Guidelines (with git workflow)",
39
+ # "License",
40
+ # "Acknowledgements",
41
+ # ]
42
+
43
+ # LOADER_HTML = """
44
+ # <div style="display:flex;flex-direction:column;align-items:center;justify-content:center;
45
+ # gap:16px;padding:32px 0 24px;font-family:'Fira Code',monospace;">
46
+ # <div style="width:54px;height:54px;border-radius:50%;
47
+ # border:3px solid #1f2840;
48
+ # border-top-color:#3cffd0;
49
+ # border-right-color:#f0c060;
50
+ # animation:rfSpin .85s linear infinite;"></div>
51
+ # <div style="color:#3cffd0;font-size:.76rem;letter-spacing:2.5px;
52
+ # text-transform:uppercase;animation:rfPulse 1.4s ease-in-out infinite;">
53
+ # Forging your README&hellip;
54
+ # </div>
55
+ # <div style="display:flex;gap:8px;">
56
+ # <span style="width:8px;height:8px;border-radius:50%;background:#f0c060;
57
+ # animation:rfBounce 1.1s ease-in-out infinite;animation-delay:0s;"></span>
58
+ # <span style="width:8px;height:8px;border-radius:50%;background:#3cffd0;
59
+ # animation:rfBounce 1.1s ease-in-out infinite;animation-delay:.18s;"></span>
60
+ # <span style="width:8px;height:8px;border-radius:50%;background:#ff5e7a;
61
+ # animation:rfBounce 1.1s ease-in-out infinite;animation-delay:.36s;"></span>
62
+ # </div>
63
+ # </div>
64
+ # <style>
65
+ # @keyframes rfSpin { to { transform: rotate(360deg); } }
66
+ # @keyframes rfPulse { 0%,100%{opacity:.35} 50%{opacity:1} }
67
+ # @keyframes rfBounce { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-8px)} }
68
+ # </style>
69
+ # """
70
+
71
+
72
+ # # ─────────────────────────────────────────
73
+ # # GitHub helpers
74
+ # # ─────────────────────────────────────────
75
+ # def gh_headers():
76
+ # h = {"Accept": "application/vnd.github+json"}
77
+ # if GITHUB_TOKEN:
78
+ # h["Authorization"] = f"Bearer {GITHUB_TOKEN}"
79
+ # return h
80
+
81
+ # def parse_repo_url(url: str):
82
+ # url = url.strip().rstrip("/")
83
+ # m = re.search(r"github\.com[:/]([^/]+)/([^/\s]+?)(?:\.git)?$", url)
84
+ # if m:
85
+ # return m.group(1), m.group(2)
86
+ # return None, None
87
+
88
+ # def fetch_repo_meta(owner, repo):
89
+ # r = requests.get(f"https://api.github.com/repos/{owner}/{repo}",
90
+ # headers=gh_headers(), timeout=10)
91
+ # r.raise_for_status()
92
+ # return r.json()
93
+
94
+ # def fetch_tree(owner, repo, branch="HEAD"):
95
+ # r = requests.get(
96
+ # f"https://api.github.com/repos/{owner}/{repo}/git/trees/{branch}?recursive=1",
97
+ # headers=gh_headers(), timeout=10,
98
+ # )
99
+ # if r.status_code != 200:
100
+ # return []
101
+ # return [item["path"] for item in r.json().get("tree", []) if item["type"] == "blob"]
102
+
103
+ # def fetch_file(owner, repo, path):
104
+ # import base64
105
+ # r = requests.get(
106
+ # f"https://api.github.com/repos/{owner}/{repo}/contents/{path}",
107
+ # headers=gh_headers(), timeout=10,
108
+ # )
109
+ # if r.status_code != 200:
110
+ # return ""
111
+ # try:
112
+ # return base64.b64decode(r.json().get("content", "")).decode("utf-8", errors="ignore")[:2000]
113
+ # except Exception:
114
+ # return ""
115
+
116
+ # def fetch_languages(owner, repo):
117
+ # r = requests.get(f"https://api.github.com/repos/{owner}/{repo}/languages",
118
+ # headers=gh_headers(), timeout=10)
119
+ # return list(r.json().keys()) if r.status_code == 200 else []
120
+
121
+ # def build_folder_tree(paths, max_depth=3):
122
+ # tree = {}
123
+ # for path in paths:
124
+ # parts = path.split("/")
125
+ # if len(parts) > max_depth:
126
+ # parts = parts[:max_depth]
127
+ # node = tree
128
+ # for p in parts:
129
+ # node = node.setdefault(p, {})
130
+ # lines = []
131
+ # def render(node, prefix=""):
132
+ # items = list(node.items())
133
+ # for i, (name, children) in enumerate(items):
134
+ # is_last = i == len(items) - 1
135
+ # lines.append(prefix + ("└── " if is_last else "β”œβ”€β”€ ") + name)
136
+ # if children:
137
+ # render(children, prefix + (" " if is_last else "β”‚ "))
138
+ # render(tree)
139
+ # return "\n".join(lines)
140
+
141
+
142
+ # # ─────────────────────────────────────────
143
+ # # OpenRouter
144
+ # # ─────────────────────────────────────────
145
+ # def call_openrouter(prompt: str) -> str:
146
+ # if not OPENROUTER_API_KEY:
147
+ # return "ERROR: OPENROUTER_API_KEY secret not set in HF Space settings."
148
+ # r = requests.post(
149
+ # "https://openrouter.ai/api/v1/chat/completions",
150
+ # headers={"Authorization": f"Bearer {OPENROUTER_API_KEY}",
151
+ # "Content-Type": "application/json"},
152
+ # json={
153
+ # "model": OPENROUTER_MODEL,
154
+ # "max_tokens": 4096,
155
+ # "messages": [
156
+ # {"role": "system", "content": SYSTEM_PROMPT},
157
+ # {"role": "user", "content": prompt},
158
+ # ],
159
+ # },
160
+ # timeout=90,
161
+ # )
162
+ # r.raise_for_status()
163
+ # obj = r.json()
164
+ # if "choices" in obj and obj["choices"]:
165
+ # return obj["choices"][0]["message"]["content"].strip()
166
+ # return "[No response from model]"
167
+
168
+
169
+ # # ─────────────────────────────────────────
170
+ # # Main generator β€” plain function (no yield)
171
+ # # Returns: (loader_update, status_str, readme_str)
172
+ # # ─────────────────────────────────────────
173
+ # def generate_readme(repo_url, license_choice, extra_context, sections):
174
+ # hide = gr.update(visible=False)
175
+
176
+ # if not repo_url.strip():
177
+ # return hide, "Please paste a GitHub repository URL.", ""
178
+
179
+ # owner, repo = parse_repo_url(repo_url)
180
+ # if not owner:
181
+ # return hide, "Could not parse GitHub URL. Use: https://github.com/owner/repo", ""
182
+
183
+ # log = []
184
+ # log.append(f"Fetching {owner}/{repo} from GitHub...")
185
+
186
+ # try:
187
+ # meta = fetch_repo_meta(owner, repo)
188
+ # languages = fetch_languages(owner, repo)
189
+ # all_files = fetch_tree(owner, repo, meta.get("default_branch", "HEAD"))
190
+ # except Exception as e:
191
+ # return hide, "\n".join(log) + f"\nGitHub API error: {e}", ""
192
+
193
+ # log.append(f"Got {len(all_files)} files. Languages: {', '.join(languages) or 'unknown'}")
194
+
195
+ # key_files = ["requirements.txt", "pyproject.toml", "package.json",
196
+ # "Cargo.toml", "setup.py", "Dockerfile", ".env.example", "Makefile"]
197
+ # snippets = ""
198
+ # for kf in key_files:
199
+ # if kf in all_files:
200
+ # content = fetch_file(owner, repo, kf)
201
+ # if content:
202
+ # snippets += f"\n\n### {kf}\n```\n{content[:600]}\n```"
203
+
204
+ # folder_tree = build_folder_tree(all_files, max_depth=3)
205
+ # sections_str = "\n".join(f"- {s}" for s in (sections or ALL_SECTIONS))
206
+
207
+ # prompt = f"""Generate a complete GitHub README.md for this repository.
208
+
209
+ # ## Repository Info
210
+ # - **Name:** {meta.get('name', repo)}
211
+ # - **Owner:** {owner}
212
+ # - **Description:** {meta.get('description') or extra_context or 'No description provided'}
213
+ # - **Primary Language:** {meta.get('language') or 'Unknown'}
214
+ # - **All Languages:** {', '.join(languages) or 'Unknown'}
215
+ # - **Stars:** {meta.get('stargazers_count', 0)} | **Forks:** {meta.get('forks_count', 0)}
216
+ # - **Default Branch:** {meta.get('default_branch', 'main')}
217
+ # - **License:** {license_choice}
218
+ # - **Topics:** {', '.join(meta.get('topics', [])) or 'none'}
219
+ # - **Homepage:** {meta.get('homepage') or 'none'}
220
+
221
+ # ## Folder Structure (actual repo files)
222
+ # ```
223
+ # {repo}/
224
+ # {folder_tree[:2000]}
225
+ # ```
226
+
227
+ # ## Key File Contents{snippets}
228
+
229
+ # ## Extra Context from User
230
+ # {extra_context or 'None provided'}
231
+
232
+ # ## Required Sections β€” include ALL of these
233
+ # {sections_str}
234
+
235
+ # Use **{license_choice}** license in the License section.
236
+
237
+ # Now generate the full README.md:"""
238
+
239
+ # log.append("Calling AI model... (this may take 15-30s)")
240
+
241
+ # try:
242
+ # readme = call_openrouter(prompt)
243
+ # except Exception as e:
244
+ # return hide, "\n".join(log) + f"\nAI error: {e}", ""
245
+
246
+ # log.append("Done! Your README is ready below.")
247
+ # return hide, "\n".join(log), readme
248
+
249
+
250
+ # # ─────────────────────────────────────────
251
+ # # CSS
252
+ # # ─────────────────────────────────────────
253
+ # CSS = """
254
+ # @import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Outfit:wght@300;400;500;600&family=Fira+Code:wght@300;400;500&display=swap');
255
+ # :root {
256
+ # --ink: #0b0e14;
257
+ # --paper: #111520;
258
+ # --card: #161b27;
259
+ # --rim: #1f2840;
260
+ # --gold: #f0c060;
261
+ # --teal: #3cffd0;
262
+ # --rose: #ff5e7a;
263
+ # --dim: #4a5570;
264
+ # --body: #c8cfe0;
265
+ # }
266
+ # *, *::before, *::after { box-sizing: border-box; }
267
+ # body, .gradio-container {
268
+ # background: var(--ink) !important;
269
+ # color: var(--body) !important;
270
+ # font-family: 'Outfit', sans-serif !important;
271
+ # }
272
+ # .gradio-container { max-width: 1180px !important; margin: 0 auto !important; }
273
+ # .hdr {
274
+ # display:flex; align-items:center; gap:24px;
275
+ # padding:38px 0 28px;
276
+ # border-bottom:1px solid var(--rim);
277
+ # margin-bottom:32px;
278
+ # }
279
+ # .hdr-icon { font-size:3.2rem; line-height:1; filter:drop-shadow(0 0 18px rgba(240,192,96,.45)); }
280
+ # .hdr-text h1 {
281
+ # font-family:'Bebas Neue',sans-serif;
282
+ # font-size:3rem; letter-spacing:3px;
283
+ # color:var(--gold); margin:0 0 4px;
284
+ # text-shadow:0 0 30px rgba(240,192,96,.3);
285
+ # }
286
+ # .hdr-text p { color:var(--dim); font-size:.82rem; margin:0; letter-spacing:.5px; }
287
+ # .lbl {
288
+ # font-family:'Fira Code',monospace;
289
+ # font-size:.68rem; letter-spacing:2.5px;
290
+ # text-transform:uppercase; color:var(--teal);
291
+ # margin-bottom:10px;
292
+ # }
293
+ # textarea, input[type="text"] {
294
+ # background:var(--card) !important;
295
+ # border:1px solid var(--rim) !important;
296
+ # border-radius:6px !important;
297
+ # color:var(--body) !important;
298
+ # font-family:'Fira Code',monospace !important;
299
+ # font-size:.83rem !important;
300
+ # transition:border-color .18s, box-shadow .18s !important;
301
+ # }
302
+ # textarea:focus, input[type="text"]:focus {
303
+ # border-color:var(--teal) !important;
304
+ # box-shadow:0 0 0 2px rgba(60,255,208,.08) !important;
305
+ # }
306
+ # .status-box textarea {
307
+ # background:var(--paper) !important;
308
+ # border-color:var(--rim) !important;
309
+ # font-family:'Fira Code',monospace !important;
310
+ # font-size:.78rem !important;
311
+ # color:var(--teal) !important;
312
+ # }
313
+ # .out-box textarea {
314
+ # background:var(--paper) !important;
315
+ # border:1px solid var(--rim) !important;
316
+ # font-family:'Fira Code',monospace !important;
317
+ # font-size:.8rem !important;
318
+ # color:#e0e8ff !important;
319
+ # line-height:1.55 !important;
320
+ # }
321
+ # .divider {
322
+ # height:1px;
323
+ # background:linear-gradient(90deg,transparent,var(--rim),transparent);
324
+ # margin:6px 0 20px;
325
+ # }
326
+ # .tip {
327
+ # background:var(--card);
328
+ # border:1px solid var(--rim);
329
+ # border-left:3px solid var(--gold);
330
+ # border-radius:6px;
331
+ # padding:11px 15px;
332
+ # font-size:.78rem;
333
+ # color:var(--dim);
334
+ # line-height:1.65;
335
+ # margin-bottom:16px;
336
+ # font-family:'Fira Code',monospace;
337
+ # }
338
+ # .ftr {
339
+ # text-align:center; margin-top:36px;
340
+ # color:#1e2535; font-size:.68rem;
341
+ # font-family:'Fira Code',monospace;
342
+ # letter-spacing:.5px;
343
+ # }
344
+ # """
345
+
346
+ # # ─────────────────────────────────────────
347
+ # # UI
348
+ # # ─────────────────────────────────────────
349
+ # with gr.Blocks(title="README Forge", css=CSS,
350
+ # theme=gr.themes.Base(primary_hue="emerald", neutral_hue="slate")) as demo:
351
+
352
+ # gr.HTML("""
353
+ # <div class="hdr">
354
+ # <div class="hdr-icon">πŸ“œ</div>
355
+ # <div class="hdr-text">
356
+ # <h1>README FORGE</h1>
357
+ # <p>Paste a GitHub repo URL &rarr; get a production-ready README in seconds</p>
358
+ # </div>
359
+ # </div>
360
+ # """)
361
+
362
+ # with gr.Row(equal_height=False):
363
+
364
+ # # LEFT panel
365
+ # with gr.Column(scale=1, min_width=320):
366
+ # gr.HTML('<div class="lbl">Configuration</div><div class="divider"></div>')
367
+ # gr.HTML('<div class="tip">Paste any public GitHub URL. Real file trees, languages and metadata are fetched automatically.</div>')
368
+
369
+ # repo_url = gr.Textbox(
370
+ # label="GitHub Repository URL",
371
+ # placeholder="https://github.com/owner/repository",
372
+ # lines=1,
373
+ # )
374
+ # license_choice = gr.Dropdown(
375
+ # label="License",
376
+ # choices=["MIT", "Apache 2.0", "GPL-3.0", "BSD-2-Clause", "AGPL-3.0", "Unlicense"],
377
+ # value="MIT",
378
+ # )
379
+ # extra_context = gr.Textbox(
380
+ # label="Extra Context (optional)",
381
+ # placeholder="What does it do? Any special details the AI should know?",
382
+ # lines=3,
383
+ # )
384
+ # gr.HTML('<div class="lbl" style="margin-top:18px;">Sections to Include</div>')
385
+ # sections = gr.CheckboxGroup(
386
+ # choices=ALL_SECTIONS,
387
+ # value=ALL_SECTIONS,
388
+ # label="",
389
+ # interactive=True,
390
+ # )
391
+ # with gr.Row():
392
+ # gen_btn = gr.Button("Generate README", variant="primary", size="lg")
393
+ # clear_btn = gr.Button("Clear", variant="secondary")
394
+
395
+ # # RIGHT panel
396
+ # with gr.Column(scale=2):
397
+
398
+ # loader = gr.HTML(value="", visible=False)
399
+
400
+ # gr.HTML('<div class="lbl">Status</div><div class="divider"></div>')
401
+ # status_box = gr.Textbox(
402
+ # label="", interactive=False, lines=4,
403
+ # placeholder="Waiting for input...",
404
+ # elem_classes=["status-box"],
405
+ # )
406
+ # gr.HTML('<div class="lbl" style="margin-top:20px;">Generated README.md</div><div class="divider"></div>')
407
+ # output_box = gr.Textbox(
408
+ # label="", interactive=False, lines=30,
409
+ # placeholder="Your README will appear here β€” ready to copy and paste into GitHub.",
410
+ # show_copy_button=True,
411
+ # elem_classes=["out-box"],
412
+ # )
413
+
414
+ # gr.HTML('<div class="ftr">README Forge &nbsp;&middot;&nbsp; Powered by OpenRouter &middot; nvidia/nemotron-nano-12b &middot; GitHub API</div>')
415
+
416
+ # # Show loader β†’ run generation (which returns loader=hidden) β†’ done
417
+ # gen_btn.click(
418
+ # fn=lambda: gr.update(value=LOADER_HTML, visible=True),
419
+ # inputs=None,
420
+ # outputs=loader,
421
+ # ).then(
422
+ # fn=generate_readme,
423
+ # inputs=[repo_url, license_choice, extra_context, sections],
424
+ # outputs=[loader, status_box, output_box],
425
+ # )
426
+
427
+ # clear_btn.click(
428
+ # fn=lambda: ("", "MIT", "", ALL_SECTIONS, "", ""),
429
+ # outputs=[repo_url, license_choice, extra_context, sections, status_box, output_box],
430
+ # )
431
+
432
+ # if __name__ == "__main__":
433
+ # demo.launch(server_name="0.0.0.0", server_port=7860, debug=True)
434
+
435
  import os
436
  import re
437
  import requests
 
502
  </style>
503
  """
504
 
 
505
  # ─────────────────────────────────────────
506
  # GitHub helpers
507
  # ─────────────────────────────────────────
 
571
  render(tree)
572
  return "\n".join(lines)
573
 
 
574
  # ─────────────────────────────────────────
575
  # OpenRouter
576
  # ─────────────────────────────────────────
577
  def call_openrouter(prompt: str) -> str:
578
  if not OPENROUTER_API_KEY:
579
+ return "ERROR: OPENROUTER_API_KEY secret not set."
580
  r = requests.post(
581
  "https://openrouter.ai/api/v1/chat/completions",
582
  headers={"Authorization": f"Bearer {OPENROUTER_API_KEY}",
 
597
  return obj["choices"][0]["message"]["content"].strip()
598
  return "[No response from model]"
599
 
 
600
  # ─────────────────────────────────────────
601
+ # Generator β€” yields status updates live
 
602
  # ─────────────────────────────────────────
603
  def generate_readme(repo_url, license_choice, extra_context, sections):
604
+ # Each yield: (loader_html, status_text, readme_text)
605
 
606
  if not repo_url.strip():
607
+ yield gr.update(visible=False), "Please paste a GitHub repository URL.", ""
608
+ return
609
 
610
  owner, repo = parse_repo_url(repo_url)
611
  if not owner:
612
+ yield gr.update(visible=False), "Could not parse GitHub URL. Use: https://github.com/owner/repo", ""
613
+ return
614
 
615
  log = []
616
+
617
+ log.append("πŸ” Fetching repo metadata from GitHub...")
618
+ yield gr.update(value=LOADER_HTML, visible=True), "\n".join(log), ""
619
 
620
  try:
621
  meta = fetch_repo_meta(owner, repo)
622
  languages = fetch_languages(owner, repo)
623
  all_files = fetch_tree(owner, repo, meta.get("default_branch", "HEAD"))
624
  except Exception as e:
625
+ yield gr.update(visible=False), f"GitHub API error: {e}", ""
626
+ return
627
 
628
+ log.append(f"βœ… Got {len(all_files)} files Β· Languages: {', '.join(languages) or 'unknown'}")
629
+ yield gr.update(value=LOADER_HTML, visible=True), "\n".join(log), ""
630
 
631
  key_files = ["requirements.txt", "pyproject.toml", "package.json",
632
  "Cargo.toml", "setup.py", "Dockerfile", ".env.example", "Makefile"]
 
672
 
673
  Now generate the full README.md:"""
674
 
675
+ log.append("πŸ€– AI is writing your README... (15–30s)")
676
+ yield gr.update(value=LOADER_HTML, visible=True), "\n".join(log), ""
677
 
678
  try:
679
  readme = call_openrouter(prompt)
680
  except Exception as e:
681
+ yield gr.update(visible=False), "\n".join(log) + f"\nAI error: {e}", ""
682
+ return
683
 
684
+ log.append("βœ… Done! Your README is ready below.")
685
+ yield gr.update(visible=False), "\n".join(log), readme
686
 
687
 
688
  # ─────────────────────────────────────────
 
756
  color:#e0e8ff !important;
757
  line-height:1.55 !important;
758
  }
759
+ .divider { height:1px; background:linear-gradient(90deg,transparent,var(--rim),transparent); margin:6px 0 20px; }
 
 
 
 
760
  .tip {
761
+ background:var(--card); border:1px solid var(--rim);
762
+ border-left:3px solid var(--gold); border-radius:6px;
763
+ padding:11px 15px; font-size:.78rem; color:var(--dim);
764
+ line-height:1.65; margin-bottom:16px; font-family:'Fira Code',monospace;
 
 
 
 
 
 
 
 
 
 
 
 
765
  }
766
+ .ftr { text-align:center; margin-top:36px; color:#1e2535; font-size:.68rem; font-family:'Fira Code',monospace; letter-spacing:.5px; }
767
  """
768
 
769
  # ─────────────────────────────────────────
 
784
 
785
  with gr.Row(equal_height=False):
786
 
 
787
  with gr.Column(scale=1, min_width=320):
788
  gr.HTML('<div class="lbl">Configuration</div><div class="divider"></div>')
789
  gr.HTML('<div class="tip">Paste any public GitHub URL. Real file trees, languages and metadata are fetched automatically.</div>')
 
811
  interactive=True,
812
  )
813
  with gr.Row():
814
+ gen_btn = gr.Button("⚑ Generate README", variant="primary", size="lg")
815
+ clear_btn = gr.Button("β†Ί Clear", variant="secondary")
816
 
 
817
  with gr.Column(scale=2):
818
 
819
  loader = gr.HTML(value="", visible=False)
 
832
  elem_classes=["out-box"],
833
  )
834
 
835
+ gr.HTML('<div class="ftr">README Forge &nbsp;&middot;&nbsp; OpenRouter &middot; nvidia/nemotron-nano-12b &middot; GitHub API</div>')
836
 
 
837
  gen_btn.click(
 
 
 
 
838
  fn=generate_readme,
839
  inputs=[repo_url, license_choice, extra_context, sections],
840
  outputs=[loader, status_box, output_box],