diff --git a/src/gui/components.py b/src/gui/components.py index 10d7c38..4c52fab 100644 --- a/src/gui/components.py +++ b/src/gui/components.py @@ -903,3 +903,76 @@ def findings_count_for_tool(tool_id: str) -> int: """ findings = st.session_state.get("home_findings") or [] return sum(1 for f in findings if f.tool == tool_id) + + +# --------------------------------------------------------------------------- +# Cross-page upload pickup +# --------------------------------------------------------------------------- + +class _StashedUpload: + """Duck-types ``st.runtime.uploaded_file_manager.UploadedFile`` enough + for the tool pages: ``.name``, ``.size``, ``.getvalue()``. + + Tool pages that previously consumed a Streamlit ``UploadedFile`` can + accept this in its place without changes. + """ + + __slots__ = ("name", "size", "_data") + + def __init__(self, name: str, data: bytes) -> None: + self.name = name + self.size = len(data) + self._data = data + + def getvalue(self) -> bytes: + return self._data + + def read(self) -> bytes: + return self._data + + +def pickup_or_upload( + *, + label: str, + key: str, + types: list[str], + help: str | None = None, +): + """Return an upload object, preferring the home-page upload when present. + + Behavior: + + - If ``st.session_state['home_uploaded_bytes']`` is set and the user + hasn't asked for a different file on this page, render a banner + ("Using ** from upload screen") plus a "Use a different file" + button, and return a :class:`_StashedUpload` shim. + - Otherwise render the standard ``st.file_uploader`` with the supplied + *label*, *key*, and *types*. Returns the Streamlit ``UploadedFile`` + directly (or ``None`` if nothing uploaded). + + The ``_StashedUpload`` shim exposes ``.name``, ``.size``, and + ``.getvalue()`` so existing tool-page code that consumes a Streamlit + upload object works without changes. + """ + override_key = f"{key}__override" + has_session_upload = st.session_state.get("home_uploaded_bytes") is not None + use_session = has_session_upload and not st.session_state.get(override_key, False) + + if use_session: + name = st.session_state.get("home_uploaded_name", "uploaded file") + st.info(f"Using **{name}** from the upload screen.") + if st.button("Use a different file", key=f"{key}__pick_diff"): + st.session_state[override_key] = True + st.rerun() + return _StashedUpload(name, st.session_state["home_uploaded_bytes"]) + + uploaded = st.file_uploader(label, type=types, key=key, help=help) + if uploaded is not None and st.session_state.get(override_key): + # User has uploaded their own file on this page; clear the override + # so the next visit to a tool page starts fresh. + pass + if uploaded is None and st.session_state.get(override_key) and has_session_upload: + if st.button("Switch back to upload-screen file", key=f"{key}__switch_back"): + st.session_state[override_key] = False + st.rerun() + return uploaded diff --git a/src/gui/pages/1_Deduplicator.py b/src/gui/pages/1_Deduplicator.py index 5356bf8..6fa8760 100644 --- a/src/gui/pages/1_Deduplicator.py +++ b/src/gui/pages/1_Deduplicator.py @@ -21,6 +21,7 @@ from src.gui.components import ( config_panel, hide_streamlit_chrome, match_group_card, + pickup_or_upload, results_summary, ) @@ -56,11 +57,11 @@ st.caption("Find and remove duplicate rows in CSV, delimited text, and Excel fil # File upload # --------------------------------------------------------------------------- -uploaded = st.file_uploader( - "Upload CSV or Excel file", - type=["csv", "tsv", "xlsx", "xls"], - help="Supports CSV, TSV, and Excel files. Encoding and delimiters are auto-detected.", +uploaded = pickup_or_upload( + label="Upload CSV or Excel file", key="dedup_file_upload", + types=["csv", "tsv", "xlsx", "xls"], + help="Supports CSV, TSV, and Excel files. Encoding and delimiters are auto-detected.", ) if uploaded is not None: diff --git a/src/gui/pages/2_Text_Cleaner.py b/src/gui/pages/2_Text_Cleaner.py index a09dc96..ab0b8fb 100644 --- a/src/gui/pages/2_Text_Cleaner.py +++ b/src/gui/pages/2_Text_Cleaner.py @@ -14,7 +14,7 @@ _project_root = Path(__file__).resolve().parent.parent.parent.parent if str(_project_root) not in sys.path: sys.path.insert(0, str(_project_root)) -from src.gui.components import hide_streamlit_chrome +from src.gui.components import hide_streamlit_chrome, pickup_or_upload from src.core.text_clean import ( PRESETS, CleanOptions, @@ -38,10 +38,10 @@ st.caption( # File upload # --------------------------------------------------------------------------- -uploaded = st.file_uploader( - "Upload CSV or Excel file", - type=["csv", "tsv", "xlsx", "xls"], +uploaded = pickup_or_upload( + label="Upload CSV or Excel file", key="textclean_file_upload", + types=["csv", "tsv", "xlsx", "xls"], ) if uploaded is None: