import time from collections.abc import Generator from concurrent.futures import ThreadPoolExecutor import gradio as gr import requests from src.analyzer import count_lines_by_language from src.constants import ( APP_SUBTITLE, APP_TITLE, BG_COLOR, C_ACCENT, C_LINK, C_MUTED, C_TEXT, FONT_FAMILY, FONT_SIZE, GITHUB_URL, PROGRESS_RENDER_INTERVAL, ) from src.github import parse_repo_url from src.models import ProgressState, RepoStats from src.rendering import _error_html, render_html def analyze_repo(url: str) -> Generator[str, None, None]: if not url or not url.strip(): yield _error_html("Enter a GitHub repository URL or owner/repo.") return try: owner, repo = parse_repo_url(url) state = ProgressState() def _run() -> RepoStats: return count_lines_by_language(owner, repo, state=state) with ThreadPoolExecutor(max_workers=1) as executor: future = executor.submit(_run) while not future.done(): time.sleep(PROGRESS_RENDER_INTERVAL) with state.lock: snap_langs = dict(state.languages) snap_completed = state.completed snap_total = state.total snap_commits = state.commits snap_branches = state.branches snap_histogram = state.histogram if snap_total > 0: yield render_html( snap_langs, owner=owner, repo=repo, completed=snap_completed, total=snap_total, commits=snap_commits, branches=snap_branches, histogram=snap_histogram, ) stats = future.result() yield render_html( stats.languages, owner=owner, repo=repo, completed=stats.files, total=stats.files, commits=stats.commits, branches=stats.branches, histogram=stats.histogram, ) except ValueError as e: yield _error_html(str(e)) except requests.ConnectionError: yield _error_html("Failed to connect to GitHub. Check your network connection.") except requests.Timeout: yield _error_html("Request timed out. Try again later.") except Exception as e: yield _error_html(f"Unexpected error: {e}") _CSS = f""" * {{ border-radius: 0 !important; }} .gradio-container {{ max-width: 100% !important; padding: 8px 0 !important; background: {BG_COLOR} !important; font-family: {FONT_FAMILY} !important; }} .main, .contain, .wrap, .block {{ background: transparent !important; padding: 0 !important; max-width: 100% !important; border: none !important; }} .gradio-container [class*="html-container"] {{ padding: 0 !important; }} .column {{ gap: 4px !important; padding: 0 16px 0 2ch !important; }} footer, header {{ display: none !important; }} #app-title-wrap {{ padding: 0 !important; background: transparent !important; }} #app-title-wrap > div {{ background: transparent !important; }} /* kill all gradio wrapper chrome on the input */ #cli {{ position: relative; padding-left: 20ch !important; background: transparent !important; border: none !important; box-shadow: none !important; outline: none !important; overflow: visible !important; }} #cli label, #cli .input-container {{ background: transparent !important; border: none !important; box-shadow: none !important; outline: none !important; }} #cli .label-wrap {{ display: none !important; }} #cli textarea, #cli input {{ font-family: {FONT_FAMILY} !important; font-size: {FONT_SIZE} !important; background: transparent !important; color: {C_ACCENT} !important; border: none !important; box-shadow: none !important; outline: none !important; padding: 8px 0 !important; caret-color: transparent; }} @keyframes blink {{ 50% {{ opacity: 0; }} }} #cli-cursor {{ position: absolute; left: 20ch; top: 50%; transform: translateY(-50%); color: {C_ACCENT}; font-family: {FONT_FAMILY}; font-size: {FONT_SIZE}; pointer-events: none; z-index: 1; animation: blink 1s step-end infinite; }} #cli::before {{ content: '\\00a0github repository:'; position: absolute; left: 0; top: 50%; transform: translateY(-50%); color: {C_TEXT}; font-family: {FONT_FAMILY}; font-size: {FONT_SIZE}; z-index: 1; pointer-events: none; }} """ _HEAD = """ """ _TITLE_HTML = ( f'
' f'{APP_TITLE}' f"
" f'{APP_SUBTITLE}' f"

" f"
" ) def create_app() -> gr.Blocks: with gr.Blocks( title=APP_TITLE, ) as app: gr.HTML(_TITLE_HTML, elem_id="app-title-wrap") url_input = gr.Textbox( show_label=False, placeholder="owner/repo", elem_id="cli", container=False, autofocus=True, ) output = gr.HTML() url_input.submit(fn=analyze_repo, inputs=url_input, outputs=output) return app def launch_app(app: gr.Blocks) -> None: app.launch( theme=gr.themes.Monochrome(), # type: ignore[attr-defined] css=_CSS, head=_HEAD, )