import gradio as gr import requests import zipfile import io import os import tempfile from pypdf import PdfReader # Video URL of The Count VIDEO_URL = "https://images-ext-1.discordapp.net/external/PD3SdBHrOGxZtFU8zypW2PjGXabBG-OwY3rXB8XWG7Y/https/media.tenor.com/1nFPylOyXZIAAAPo/sesame-street-the-count.mp4" def get_count_response(count): if count == 0: return "Zero! Zero em dashes! Ah ah ah!" elif count == 1: return "One! One em dash! Ah ah ah!" elif count <= 20: counting = " ".join([f"{i}!" for i in range(1, count + 1)]) return f"{counting} {count} em dashes! Ah ah ah!" else: return f"One! Two! Three! ... {count}! {count} em dashes! Ah ah ah!" def find_em_dashes_in_bytes(content_bytes, filename): locations = [] ext = os.path.splitext(filename.lower())[1] if ext == '.pdf': try: reader = PdfReader(io.BytesIO(content_bytes)) for page_number, page in enumerate(reader.pages, start=1): text = page.extract_text() if text: for line_number, line in enumerate(text.splitlines(), start=1): count = line.count('—') if count: locations.append({ "file": filename, "page": page_number, "line": line_number, "count": count, "text": line.strip(), }) except Exception: pass else: # Assume text-based for other common extensions try: content = content_bytes.decode('utf-8', errors='ignore') for line_number, line in enumerate(content.splitlines(), start=1): count = line.count('—') if count: locations.append({ "file": filename, "page": None, "line": line_number, "count": count, "text": line.strip(), }) except Exception: pass return locations def markdown_code(text): return text.replace("`", "ˋ") def create_audit_markdown(locations): total_count = sum(item["count"] for item in locations) lines = [ "# Em Dash Audit", "", "> **Instruction for the LLM agent:** Audit every em dash at the locations listed below, including its surrounding text and repository context. Report recommended edits that could clean up the repository. **Do not edit any files during this audit.** Only recommend changes and provide a plan for applying them.", "", f"**Summary:** {total_count} em dash{'es' if total_count != 1 else ''} across {len(locations)} line{'s' if len(locations) != 1 else ''}.", "", "## Locations", "", ] if not locations: lines.append("No em dashes were found.") else: current_file = None for item in locations: if item["file"] != current_file: current_file = item["file"] lines.extend([f"### `{markdown_code(current_file)}`", ""]) position = f"Page {item['page']}, line {item['line']}" if item["page"] else f"Line {item['line']}" occurrence_label = f" — {item['count']} occurrences" if item["count"] > 1 else "" lines.append(f"- **{position}**{occurrence_label}: `{markdown_code(item['text'])}`") lines.append("") with tempfile.NamedTemporaryFile( mode="w", encoding="utf-8", suffix=".md", prefix="em-dash-audit-", delete=False, ) as audit_file: audit_file.write("\n".join(lines)) return audit_file.name def process_input(input_url, uploaded_file): locations = [] processed = False # Handle Uploaded File if uploaded_file is not None: with open(uploaded_file.name, "rb") as f: file_bytes = f.read() locations.extend(find_em_dashes_in_bytes(file_bytes, os.path.basename(uploaded_file.name))) processed = True # Handle URL if input_url and input_url.strip(): url = input_url.strip() is_github = "github.com" in url is_hf = "huggingface.co" in url # Check if it's likely a Repo (GitHub or Hugging Face) if (is_github or is_hf) and "/archive/" not in url and not any(url.lower().endswith(ext) for ext in ['.pdf', '.txt', '.md', '.py', '.js']): if is_github: # Normalize GitHub URL base_url = url.rstrip('/') if base_url.endswith('.git'): base_url = base_url[:-4] branches = ['main', 'master'] r = None for branch in branches: test_url = f"{base_url}/archive/refs/heads/{branch}.zip" try: response = requests.get(test_url, timeout=20) if response.status_code == 200: r = response break except Exception: continue if r: try: with zipfile.ZipFile(io.BytesIO(r.content)) as z: for filename in z.namelist(): if filename.endswith('/'): continue text_extensions = {'.py', '.md', '.txt', '.js', '.ts', '.html', '.css', '.c', '.cpp', '.h', '.java', '.rs', '.go', '.json', '.yml', '.yaml'} if any(filename.lower().endswith(ext) for ext in text_extensions): with z.open(filename) as f: locations.extend(find_em_dashes_in_bytes(f.read(), filename)) processed = True except Exception: pass elif is_hf: try: # Parse Hugging Face URL to get repo paths parts = url.split("huggingface.co/")[-1].strip("/").split("/") if len(parts) >= 2: if parts[0] in ["datasets", "spaces"]: repo_path = f"{parts[0]}/{parts[1]}/{parts[2]}" repo_type = parts[0] repo_id = f"{parts[1]}/{parts[2]}" else: repo_path = f"{parts[0]}/{parts[1]}" repo_type = "models" repo_id = repo_path # Fetch root directory tree via API api_url = f"https://huggingface.co/api/{repo_type}/{repo_id}/tree/main" tree_response = requests.get(api_url, timeout=20) if tree_response.status_code == 200: text_extensions = {'.py', '.md', '.txt', '.js', '.ts', '.html', '.css', '.c', '.cpp', '.h', '.java', '.rs', '.go', '.json', '.yml', '.yaml'} for item in tree_response.json(): if item.get("type") == "file": filename = item.get("path") if any(filename.lower().endswith(ext) for ext in text_extensions): # Fetch raw file content raw_url = f"https://huggingface.co/{repo_path}/resolve/main/{filename}" file_resp = requests.get(raw_url, timeout=20) if file_resp.status_code == 200: locations.extend(find_em_dashes_in_bytes(file_resp.content, filename)) processed = True except Exception: pass else: # Handle as single file URL try: response = requests.get(url, timeout=20) if response.status_code == 200: filename = url.split('/')[-1] or "file.txt" locations.extend(find_em_dashes_in_bytes(response.content, filename)) processed = True except Exception: pass if not processed: return ( "I could not find anything to count! Provide a valid URL or upload a file! Ah ah ah!", gr.update(visible=False), gr.update(value=None, visible=False), ) total_count = sum(item["count"] for item in locations) audit_path = create_audit_markdown(locations) return get_count_response(total_count), gr.update(visible=True), gr.update(value=audit_path, visible=True) # Define custom CSS for a Sesame Street / The Count theme custom_css = """ body, .gradio-container { background-color: #000000 !important; color: #e0e0e0 !important; font-family: 'Georgia', serif !important; } .gr-box { background-color: #1a0633 !important; border: 2px solid #4b0082 !important; } #large-input textarea, #large-input input { background-color: #2b0b4d !important; color: #ffffff !important; font-size: 1.5rem !important; border: 2px solid #9932cc !important; } #large-output textarea, #large-output input { background-color: #000000 !important; color: #32cd32 !important; font-size: 1.8rem !important; font-weight: bold !important; border: 3px solid #32cd32 !important; text-shadow: 2px 2px #1a0633; } #large-button { background-color: #4b0082 !important; color: #32cd32 !important; font-size: 1.6rem !important; font-weight: bold !important; border: 4px solid #32cd32 !important; height: 80px !important; box-shadow: 0 0 10px #4b0082; transition: all 0.3s ease; cursor: pointer; } #large-button:hover { background-color: #9932cc !important; color: #ffffff !important; box-shadow: 0 0 20px #32cd32; transform: scale(1.02); } .gr-form label span { font-size: 1.4rem !important; color: #9932cc !important; font-weight: bold !important; text-transform: uppercase; letter-spacing: 2px; } h1 { color: #9932cc !important; text-shadow: 2px 2px #000000 !important; font-size: 3rem !important; text-align: center !important; } h3 { color: #e0e0e0 !important; text-align: center !important; margin-bottom: 2rem !important; } .file-upload { background-color: #2b0b4d !important; border: 2px dashed #9932cc !important; } """ with gr.Blocks(title="The Count's Em Dash Counter") as demo: gr.Markdown("# 🧛‍♂️ The Count's Em Dash Counter") gr.Markdown("### Provide a GitHub repo, a file URL, or upload documents to count em dashes (—)! Ah ah ah!") with gr.Row(): with gr.Column(scale=4): repo_url = gr.Textbox( label="GitHub or File URL", placeholder="https://github.com/... OR https://example.com/file.pdf", lines=1, elem_id="large-input" ) file_upload = gr.File( label="Upload Documents (PDF, TXT, MD...)", file_types=[".pdf", ".txt", ".md", ".py", ".js", ".ts", ".html", ".css", ".json"], elem_classes="file-upload" ) count_btn = gr.Button("Count them! Ah ah ah!", variant="primary", elem_id="large-button") result_text = gr.Textbox( label="The Count Says:", interactive=False, elem_id="large-output", lines=3 ) with gr.Row(): gr.Markdown("Download a Markdown location report with audit-only instructions for an LLM agent.") audit_download = gr.DownloadButton( "DOWNLOAD", visible=False, variant="secondary", ) with gr.Column(scale=5): video = gr.Video( value=VIDEO_URL, label="The Count", autoplay=True, loop=True, show_label=False, interactive=False, visible=False ) count_btn.click( fn=process_input, inputs=[repo_url, file_upload], outputs=[result_text, video, audit_download] ) if __name__ == "__main__": demo.launch(css=custom_css)