diff --git a/app.py b/app.py index b01ac11..31f5110 100644 --- a/app.py +++ b/app.py @@ -2,15 +2,18 @@ from __future__ import annotations import contextlib import csv +import html import importlib.util import io import os import re import sys import time +from collections import deque from pathlib import Path import streamlit as st +import streamlit.components.v1 as components ROOT_DIR = Path(__file__).resolve().parent @@ -58,17 +61,26 @@ def read_csv_preview(csv_bytes: bytes, limit: int = 200) -> list[dict[str, str]] class StreamlitOutputBuffer(io.TextIOBase): - def __init__(self, placeholder, *, height: int = 220, throttle_seconds: float = 0.2) -> None: + def __init__(self, placeholder, *, height: int = 220, throttle_seconds: float = 0.2, max_lines: int = 20) -> None: self.placeholder = placeholder self.height = height self.throttle_seconds = throttle_seconds - self.parts: list[str] = [] + self.max_lines = max_lines + self.lines: deque[str] = deque(maxlen=max_lines) + self.current_line = "" self.last_render = 0.0 def write(self, text: str) -> int: if not text: return 0 - self.parts.append(text) + normalized = text.replace("\r\n", "\n").replace("\r", "\n") + for chunk in normalized.splitlines(keepends=True): + if chunk.endswith("\n"): + self.current_line += chunk[:-1] + self.lines.append(self.current_line) + self.current_line = "" + else: + self.current_line += chunk now = time.monotonic() if "\n" in text or (now - self.last_render) >= self.throttle_seconds: self.render() @@ -79,15 +91,40 @@ class StreamlitOutputBuffer(io.TextIOBase): def render(self) -> None: self.last_render = time.monotonic() - self.placeholder.text_area( - "Scan Details", - value="".join(self.parts), - height=self.height, - disabled=True, + visible_lines = list(self.lines) + if self.current_line: + visible_lines.append(self.current_line) + visible_lines = visible_lines[-self.max_lines :] + content = html.escape("\n".join(visible_lines)) + self.placeholder.caption("Scan Details") + components.html( + f""" +
{content}
+ + """, + height=self.height + 16, ) def getvalue(self) -> str: - return "".join(self.parts) + visible_lines = list(self.lines) + if self.current_line: + visible_lines.append(self.current_line) + return "\n".join(visible_lines[-self.max_lines :]) def render_sitemap_tab() -> None: