diff --git a/app.py b/app.py index 8718782..b01ac11 100644 --- a/app.py +++ b/app.py @@ -7,6 +7,7 @@ import io import os import re import sys +import time from pathlib import Path import streamlit as st @@ -56,6 +57,39 @@ def read_csv_preview(csv_bytes: bytes, limit: int = 200) -> list[dict[str, str]] return rows +class StreamlitOutputBuffer(io.TextIOBase): + def __init__(self, placeholder, *, height: int = 220, throttle_seconds: float = 0.2) -> None: + self.placeholder = placeholder + self.height = height + self.throttle_seconds = throttle_seconds + self.parts: list[str] = [] + self.last_render = 0.0 + + def write(self, text: str) -> int: + if not text: + return 0 + self.parts.append(text) + now = time.monotonic() + if "\n" in text or (now - self.last_render) >= self.throttle_seconds: + self.render() + return len(text) + + def flush(self) -> None: + self.render() + + def render(self) -> None: + self.last_render = time.monotonic() + self.placeholder.text_area( + "Scan Details", + value="".join(self.parts), + height=self.height, + disabled=True, + ) + + def getvalue(self) -> str: + return "".join(self.parts) + + def render_sitemap_tab() -> None: st.title("Sitemap Generator") st.caption("Crawl a site, export a sitemap CSV, and keep resume data inside the container data volume.") @@ -100,7 +134,9 @@ def render_sitemap_tab() -> None: else: safe_name = sanitize_job_name(job_name) output_path = SITEMAP_OUTPUT_DIR / f"{safe_name}.csv" - captured_stdout = io.StringIO() + output_placeholder = st.empty() + captured_stdout = StreamlitOutputBuffer(output_placeholder) + captured_stdout.render() try: with st.spinner("Running sitemap crawl..."): @@ -118,7 +154,9 @@ def render_sitemap_tab() -> None: resume=resume_existing, fresh=start_fresh, ) + captured_stdout.flush() except Exception as exc: + captured_stdout.flush() st.error(str(exc)) else: st.session_state["sitemap_result"] = { @@ -206,7 +244,7 @@ def render_sitemap_tab() -> None: crawl_output = (result_data.get("stdout") or "").strip() if crawl_output: - st.text_area("Crawler Output", value=crawl_output, height=220, disabled=True) + st.text_area("Scan Details", value=crawl_output, height=220, disabled=True) if log_path.exists(): log_text = log_path.read_text(encoding="utf-8", errors="replace")