Spaces:
Sleeping
Sleeping
3ssem0 commited on
Commit ·
e5c82f8
1
Parent(s): d72be96
faster-AI-1CPU
Browse files- Dockerfile +3 -2
- Procfile +2 -1
- code_commenter_improved.py +78 -31
- github_utils.py +5 -1
Dockerfile
CHANGED
|
@@ -31,6 +31,7 @@ COPY --chown=user . .
|
|
| 31 |
EXPOSE 7860
|
| 32 |
|
| 33 |
# Command to run the application using Gunicorn for better stability on HF
|
| 34 |
-
# -w
|
| 35 |
# -k uvicorn.workers.UvicornWorker: Use Uvicorn for FastAPI
|
| 36 |
-
CMD ["gunicorn", "-w", "
|
|
|
|
|
|
| 31 |
EXPOSE 7860
|
| 32 |
|
| 33 |
# Command to run the application using Gunicorn for better stability on HF
|
| 34 |
+
# -w 1: Single worker to focus all CPU power and save memory
|
| 35 |
# -k uvicorn.workers.UvicornWorker: Use Uvicorn for FastAPI
|
| 36 |
+
CMD ["gunicorn", "-w", "1", "-k", "uvicorn.workers.UvicornWorker", "main:app", "--bind", "0.0.0.0:7860", "--timeout", "120"]
|
| 37 |
+
|
Procfile
CHANGED
|
@@ -1 +1,2 @@
|
|
| 1 |
-
web: gunicorn -w
|
|
|
|
|
|
| 1 |
+
web: gunicorn -w 1 -k uvicorn.workers.UvicornWorker main:app --bind 0.0.0.0:$PORT
|
| 2 |
+
|
code_commenter_improved.py
CHANGED
|
@@ -189,6 +189,33 @@ def _invoke_ai(codet5_pipeline, prompt, max_new_tokens=60):
|
|
| 189 |
return ""
|
| 190 |
|
| 191 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 192 |
def _truncate_to_tokens(text: str, max_tokens: int = 400) -> str:
|
| 193 |
"""Approximate token limit by word count (1 token ~ 0.75 words)."""
|
| 194 |
words = text.split()
|
|
@@ -231,12 +258,18 @@ def _strip_class_attrs(tag) -> str:
|
|
| 231 |
|
| 232 |
|
| 233 |
def _annotate_html(soup, codet5_pipeline, comment_level):
|
| 234 |
-
"""Three-layer HTML annotator: Structural / Classes / Events.
|
|
|
|
|
|
|
| 235 |
event_attrs = [
|
| 236 |
"onclick", "onsubmit", "onchange", "oninput",
|
| 237 |
"onload", "onkeydown", "onmouseover",
|
| 238 |
]
|
| 239 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
for tag in soup.find_all(True):
|
| 241 |
has_event = any(tag.get(ea) for ea in event_attrs)
|
| 242 |
is_educational_target = tag.name in _TAG_PEDAGOGY
|
|
@@ -245,25 +278,54 @@ def _annotate_html(soup, codet5_pipeline, comment_level):
|
|
| 245 |
if not (is_educational_target or has_id_or_class or has_event):
|
| 246 |
continue
|
| 247 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
role = tag.name.upper()
|
| 249 |
structural_parts = []
|
|
|
|
| 250 |
pedagogy = _TAG_PEDAGOGY.get(tag.name)
|
| 251 |
if pedagogy:
|
| 252 |
structural_parts.append(pedagogy)
|
| 253 |
|
|
|
|
| 254 |
if tag.name == "img":
|
| 255 |
src = tag.get("src", "")
|
| 256 |
alt = tag.get("alt", "")
|
| 257 |
if src:
|
| 258 |
-
structural_parts.append(
|
| 259 |
-
f"source: {src if len(src) <= 60 else src[:57] + '...'}"
|
| 260 |
-
)
|
| 261 |
if alt:
|
| 262 |
structural_parts.append(f'alt text: "{alt}"')
|
| 263 |
else:
|
| 264 |
-
structural_parts.append(
|
| 265 |
-
'no alt text (needs alt="description" for accessibility)'
|
| 266 |
-
)
|
| 267 |
if tag.get("width"):
|
| 268 |
structural_parts.append(f"width: {tag.get('width')}px")
|
| 269 |
elif tag.name == "a" and tag.get("href"):
|
|
@@ -286,25 +348,13 @@ def _annotate_html(soup, codet5_pipeline, comment_level):
|
|
| 286 |
)
|
| 287 |
structural_parts.append(f"loads {lib} from CDN")
|
| 288 |
|
|
|
|
| 289 |
final_summary = ""
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
and codet5_pipeline is not None
|
| 293 |
-
and comment_level in ("detailed", "educational")
|
| 294 |
-
)
|
| 295 |
-
if should_run_model:
|
| 296 |
-
# Enforce the "WHAT, WHY, IMPACT" pedagogy.
|
| 297 |
-
# We explicitly tell it NOT to repeat the tag name to avoid the "Header: Header" redundancy.
|
| 298 |
-
prompt = (
|
| 299 |
-
f"Explain this web element for a beginner: WHAT it does, "
|
| 300 |
-
f"WHY it is placed here in context, and what would "
|
| 301 |
-
f"BREAK or happen to the visitor if it were removed. "
|
| 302 |
-
f"IMPORTANT: DO NOT start your answer with the word '{tag.name}'. "
|
| 303 |
-
f"Context: {_strip_class_attrs(tag)}"
|
| 304 |
-
)
|
| 305 |
-
raw = _invoke_ai(codet5_pipeline, prompt, max_new_tokens=100)
|
| 306 |
final_summary = _sanitize_ai_text(raw, tag.name)
|
| 307 |
|
|
|
|
| 308 |
indicator = ""
|
| 309 |
if tag.get("id"):
|
| 310 |
indicator = f"#{tag.get('id')}"
|
|
@@ -323,6 +373,7 @@ def _annotate_html(soup, codet5_pipeline, comment_level):
|
|
| 323 |
tag.insert_before(soup.new_string("\n"))
|
| 324 |
tag.insert_before(Comment(full_structural_comment))
|
| 325 |
|
|
|
|
| 326 |
if tag.get("class"):
|
| 327 |
c_val = tag.get("class")
|
| 328 |
c_str = " ".join(c_val) if isinstance(c_val, list) else c_val
|
|
@@ -333,20 +384,16 @@ def _annotate_html(soup, codet5_pipeline, comment_level):
|
|
| 333 |
parts = explained.split("\n<!-- WARNING:")
|
| 334 |
tag.insert_before(Comment(f" CLASSES: {parts[0]} "))
|
| 335 |
tag.insert_before(soup.new_string("\n"))
|
| 336 |
-
tag.insert_before(
|
| 337 |
-
Comment(
|
| 338 |
-
f" WARNING:{parts[1].replace('-->', '').strip()} "
|
| 339 |
-
)
|
| 340 |
-
)
|
| 341 |
else:
|
| 342 |
tag.insert_before(Comment(f" CLASSES: {explained} "))
|
| 343 |
|
|
|
|
| 344 |
for ea in event_attrs:
|
| 345 |
if tag.get(ea):
|
| 346 |
tag.insert_before(soup.new_string("\n"))
|
| 347 |
-
tag.insert_before(
|
| 348 |
-
|
| 349 |
-
)
|
| 350 |
|
| 351 |
|
| 352 |
CSS_PROP_COMMENTS = {
|
|
|
|
| 189 |
return ""
|
| 190 |
|
| 191 |
|
| 192 |
+
def _invoke_ai_batch(codet5_pipeline, prompts, max_new_tokens=100, batch_size=8):
|
| 193 |
+
"""Efficiently run multiple prompts through the transformer pipeline in a single batch."""
|
| 194 |
+
if codet5_pipeline is None or not prompts:
|
| 195 |
+
return [""] * len(prompts)
|
| 196 |
+
|
| 197 |
+
try:
|
| 198 |
+
# Pipeline handles list of strings and batching automatically
|
| 199 |
+
results = codet5_pipeline(
|
| 200 |
+
prompts,
|
| 201 |
+
max_new_tokens=max_new_tokens,
|
| 202 |
+
do_sample=False,
|
| 203 |
+
batch_size=batch_size
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
outputs = []
|
| 207 |
+
for res in results:
|
| 208 |
+
if isinstance(res, list):
|
| 209 |
+
outputs.append(res[0].get("generated_text", "").strip())
|
| 210 |
+
else:
|
| 211 |
+
outputs.append(res.get("generated_text", "").strip())
|
| 212 |
+
return outputs
|
| 213 |
+
except Exception as e:
|
| 214 |
+
logger.warning(f"Batch AI inference failed: {e}")
|
| 215 |
+
return [""] * len(prompts)
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
|
| 219 |
def _truncate_to_tokens(text: str, max_tokens: int = 400) -> str:
|
| 220 |
"""Approximate token limit by word count (1 token ~ 0.75 words)."""
|
| 221 |
words = text.split()
|
|
|
|
| 258 |
|
| 259 |
|
| 260 |
def _annotate_html(soup, codet5_pipeline, comment_level):
|
| 261 |
+
"""Three-layer HTML annotator: Structural / Classes / Events.
|
| 262 |
+
Uses batch inference for performance.
|
| 263 |
+
"""
|
| 264 |
event_attrs = [
|
| 265 |
"onclick", "onsubmit", "onchange", "oninput",
|
| 266 |
"onload", "onkeydown", "onmouseover",
|
| 267 |
]
|
| 268 |
|
| 269 |
+
# PASS 1: Identify targets and collect prompts
|
| 270 |
+
targets = []
|
| 271 |
+
prompts = []
|
| 272 |
+
|
| 273 |
for tag in soup.find_all(True):
|
| 274 |
has_event = any(tag.get(ea) for ea in event_attrs)
|
| 275 |
is_educational_target = tag.name in _TAG_PEDAGOGY
|
|
|
|
| 278 |
if not (is_educational_target or has_id_or_class or has_event):
|
| 279 |
continue
|
| 280 |
|
| 281 |
+
should_run_ai = (
|
| 282 |
+
(tag.name in _SEMANTIC_TAGS or tag.get("id"))
|
| 283 |
+
and codet5_pipeline is not None
|
| 284 |
+
and comment_level in ("detailed", "educational")
|
| 285 |
+
)
|
| 286 |
+
|
| 287 |
+
prompt = None
|
| 288 |
+
if should_run_ai:
|
| 289 |
+
prompt = (
|
| 290 |
+
f"Explain this web element for a beginner: WHAT it does, "
|
| 291 |
+
f"WHY it is placed here in context, and what would "
|
| 292 |
+
f"BREAK or happen to the visitor if it were removed. "
|
| 293 |
+
f"IMPORTANT: DO NOT start your answer with the word '{tag.name}'. "
|
| 294 |
+
f"Context: {_strip_class_attrs(tag)}"
|
| 295 |
+
)
|
| 296 |
+
prompts.append(prompt)
|
| 297 |
+
|
| 298 |
+
targets.append({
|
| 299 |
+
"tag": tag,
|
| 300 |
+
"has_event": has_event,
|
| 301 |
+
"prompt_idx": len(prompts) - 1 if prompt else -1
|
| 302 |
+
})
|
| 303 |
+
|
| 304 |
+
# BATCH AI INVOCATION
|
| 305 |
+
ai_outputs = []
|
| 306 |
+
if prompts:
|
| 307 |
+
ai_outputs = _invoke_ai_batch(codet5_pipeline, prompts, max_new_tokens=100)
|
| 308 |
+
|
| 309 |
+
# PASS 2: Apply annotations
|
| 310 |
+
for target in targets:
|
| 311 |
+
tag = target["tag"]
|
| 312 |
role = tag.name.upper()
|
| 313 |
structural_parts = []
|
| 314 |
+
|
| 315 |
pedagogy = _TAG_PEDAGOGY.get(tag.name)
|
| 316 |
if pedagogy:
|
| 317 |
structural_parts.append(pedagogy)
|
| 318 |
|
| 319 |
+
# Attribute-aware annotations
|
| 320 |
if tag.name == "img":
|
| 321 |
src = tag.get("src", "")
|
| 322 |
alt = tag.get("alt", "")
|
| 323 |
if src:
|
| 324 |
+
structural_parts.append(f"source: {src if len(src) <= 60 else src[:57] + '...'}")
|
|
|
|
|
|
|
| 325 |
if alt:
|
| 326 |
structural_parts.append(f'alt text: "{alt}"')
|
| 327 |
else:
|
| 328 |
+
structural_parts.append('no alt text (needs alt="description" for accessibility)')
|
|
|
|
|
|
|
| 329 |
if tag.get("width"):
|
| 330 |
structural_parts.append(f"width: {tag.get('width')}px")
|
| 331 |
elif tag.name == "a" and tag.get("href"):
|
|
|
|
| 348 |
)
|
| 349 |
structural_parts.append(f"loads {lib} from CDN")
|
| 350 |
|
| 351 |
+
# Apply AI summary
|
| 352 |
final_summary = ""
|
| 353 |
+
if target["prompt_idx"] != -1 and target["prompt_idx"] < len(ai_outputs):
|
| 354 |
+
raw = ai_outputs[target["prompt_idx"]]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 355 |
final_summary = _sanitize_ai_text(raw, tag.name)
|
| 356 |
|
| 357 |
+
# Construct final comment
|
| 358 |
indicator = ""
|
| 359 |
if tag.get("id"):
|
| 360 |
indicator = f"#{tag.get('id')}"
|
|
|
|
| 373 |
tag.insert_before(soup.new_string("\n"))
|
| 374 |
tag.insert_before(Comment(full_structural_comment))
|
| 375 |
|
| 376 |
+
# Classes annotation
|
| 377 |
if tag.get("class"):
|
| 378 |
c_val = tag.get("class")
|
| 379 |
c_str = " ".join(c_val) if isinstance(c_val, list) else c_val
|
|
|
|
| 384 |
parts = explained.split("\n<!-- WARNING:")
|
| 385 |
tag.insert_before(Comment(f" CLASSES: {parts[0]} "))
|
| 386 |
tag.insert_before(soup.new_string("\n"))
|
| 387 |
+
tag.insert_before(Comment(f" WARNING:{parts[1].replace('-->', '').strip()} "))
|
|
|
|
|
|
|
|
|
|
|
|
|
| 388 |
else:
|
| 389 |
tag.insert_before(Comment(f" CLASSES: {explained} "))
|
| 390 |
|
| 391 |
+
# Events annotation
|
| 392 |
for ea in event_attrs:
|
| 393 |
if tag.get(ea):
|
| 394 |
tag.insert_before(soup.new_string("\n"))
|
| 395 |
+
tag.insert_before(Comment(f" EVENT: triggers {tag.get(ea)} on {ea[2:]} "))
|
| 396 |
+
|
|
|
|
| 397 |
|
| 398 |
|
| 399 |
CSS_PROP_COMMENTS = {
|
github_utils.py
CHANGED
|
@@ -97,9 +97,13 @@ def get_repository(username, repo_name, token):
|
|
| 97 |
repo = g.get_repo(f"{username}/{repo_name}")
|
| 98 |
return repo
|
| 99 |
except Exception as e:
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
| 101 |
return None
|
| 102 |
|
|
|
|
| 103 |
def update_file_in_repo(repo, file_path, content, commit_message="Update file"):
|
| 104 |
"""
|
| 105 |
Update an existing file in the repository.
|
|
|
|
| 97 |
repo = g.get_repo(f"{username}/{repo_name}")
|
| 98 |
return repo
|
| 99 |
except Exception as e:
|
| 100 |
+
if "404" in str(e):
|
| 101 |
+
print(f"Info: Repository '{repo_name}' not found on GitHub. A new one will be created.")
|
| 102 |
+
else:
|
| 103 |
+
print(f"GitHub Repository lookup error: {e}")
|
| 104 |
return None
|
| 105 |
|
| 106 |
+
|
| 107 |
def update_file_in_repo(repo, file_path, content, commit_message="Update file"):
|
| 108 |
"""
|
| 109 |
Update an existing file in the repository.
|