# pages/99_File_Explorer.py # ------------------------------------------------------------------- # Universal File Explorer for the /data directory (and subfolders). # Allows browsing, viewing JSON/Markdown/text, and downloading files. # ------------------------------------------------------------------- from __future__ import annotations import os import json import mimetypes from pathlib import Path import streamlit as st # --- Page setup --- st.set_page_config(page_title="File Explorer", page_icon="📁", layout="wide") st.title("📁 File Explorer") st.caption("Browse and download files under the /data directory (e.g., cases, exports, FAISS indexes, etc.)") # --- Root directory (default /data) --- ROOT_DIR = Path("/data") if not ROOT_DIR.exists(): st.error("⚠️ /data directory not found in this environment.") st.stop() # --- Utility functions --- def list_dir(path: Path): """Return a sorted list of (name, is_dir) tuples.""" items = [] for entry in sorted(os.listdir(path)): p = path / entry if entry.startswith("."): # hide hidden files continue items.append((entry, p.is_dir())) return items def render_file(path: Path): """Render or offer download for the selected file.""" mime, _ = mimetypes.guess_type(str(path)) st.markdown(f"**Path:** `{path}` \n**Size:** {path.stat().st_size:,} bytes") if path.suffix.lower() in [".json", ".md", ".txt", ".log"]: try: text = path.read_text(encoding="utf-8") if path.suffix.lower() == ".json": st.json(json.loads(text)) elif path.suffix.lower() == ".md": st.markdown(text) else: st.code(text) except Exception as e: st.error(f"Error reading file: {e}") else: with open(path, "rb") as f: st.download_button( label="⬇️ Download File", data=f, file_name=path.name, mime=mime or "application/octet-stream", use_container_width=True, ) def export_metadata_tree(root: Path) -> dict: """Recursively export metadata (name, size, type, children).""" tree = {"name": root.name, "path": str(root), "type": "directory" if root.is_dir() else "file"} if root.is_dir(): children = [] for entry in sorted(os.listdir(root)): p = root / entry if entry.startswith("."): continue children.append(export_metadata_tree(p)) tree["children"] = children else: try: tree["size"] = root.stat().st_size except Exception: tree["size"] = None return tree # --- Sidebar navigation --- st.sidebar.header("📂 Directory Browser") def browse(path: Path): st.sidebar.markdown(f"**Current:** `{path}`") items = list_dir(path) for name, is_dir in items: sub_path = path / name if is_dir: if st.sidebar.button(f"📁 {name}", key=str(sub_path)): st.session_state["explorer_path"] = str(sub_path) else: if st.sidebar.button(f"📄 {name}", key=str(sub_path)): st.session_state["selected_file"] = str(sub_path) # --- Manage navigation state --- if "explorer_path" not in st.session_state: st.session_state["explorer_path"] = str(ROOT_DIR) if "selected_file" not in st.session_state: st.session_state["selected_file"] = "" current_path = Path(st.session_state["explorer_path"]) browse(current_path) # --- Main view --- st.divider() st.markdown(f"### 📂 {current_path}") if st.button("⬆️ Up one level", use_container_width=False): if current_path != ROOT_DIR: st.session_state["explorer_path"] = str(current_path.parent) st.session_state["selected_file"] = "" st.rerun() # --- Show directory contents --- entries = list_dir(current_path) for name, is_dir in entries: p = current_path / name icon = "📁" if is_dir else "📄" st.write(f"{icon} `{name}`") st.divider() # --- Show selected file (if any) --- selected_path = Path(st.session_state["selected_file"]) if st.session_state["selected_file"] else None if selected_path and selected_path.exists(): st.subheader(f"📄 File: {selected_path.name}") render_file(selected_path) else: st.info("Select a file in the sidebar to preview its contents.") # --- Export metadata tree --- st.divider() if st.button("📦 Export Metadata Tree", use_container_width=True): tree = export_metadata_tree(ROOT_DIR) st.download_button( "⬇️ Download Metadata (JSON)", data=json.dumps(tree, indent=2), file_name="data_directory_tree.json", mime="application/json", use_container_width=True, ) st.json(tree)