Spaces:
Sleeping
Sleeping
File size: 10,536 Bytes
3b1ae2f 883a128 5a7b0c6 be44520 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 2cc0d13 5a7b0c6 2cc0d13 5a7b0c6 aaf169f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f aaf169f 3b1ae2f aaf169f 3b1ae2f aaf169f 3b1ae2f aaf169f 3b1ae2f aaf169f 883a128 3b1ae2f aaf169f 2cc0d13 aaf169f 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 3b1ae2f 883a128 4906821 883a128 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
import streamlit as st
import os
import subprocess
from streamlit_pdf_viewer import pdf_viewer
from github import Github
import shutil # Make sure this is imported at the top
# --- Configuration ---
TOPICS_DIR = "topics"
TEMP_DIR = "temp_build"
os.makedirs(TEMP_DIR, exist_ok=True)
st.set_page_config(page_title="Physics Topics", layout="wide")
# ==========================================
# π AUTHENTICATION LOGIC (DUAL LEVEL)
# ==========================================
def check_login():
"""
Returns: 'admin', 'viewer', or None
"""
if "user_role" not in st.session_state:
st.session_state.user_role = None
if st.session_state.user_role:
return st.session_state.user_role
st.title("π Access Restricted")
with st.form("login_form"):
password_input = st.text_input("Enter Access Password", type="password")
submit_button = st.form_submit_button("Login")
if submit_button:
if password_input == st.secrets["admin_password"]:
st.session_state.user_role = "admin"
st.success("Logged in as Administrator")
st.rerun()
elif password_input == st.secrets["viewer_password"]:
st.session_state.user_role = "viewer"
st.success("Logged in as Viewer")
st.rerun()
else:
st.error("β Invalid Password")
return None
# STOP IF NOT LOGGED IN
current_role = check_login()
if not current_role:
st.stop()
# ==========================================
# π GITHUB SYNC FUNCTIONS
# ==========================================
def push_to_github(local_path, content, commit_message):
"""
Updates the file on GitHub.
local_path: 'topics/Topic1/file.tex'
"""
try:
g = Github(st.secrets["github_token"])
repo = g.get_repo(st.secrets["github_repo"])
# Get the file from the repo to retrieve its SHA (needed for update)
contents = repo.get_contents(local_path, ref=st.secrets["github_branch"])
# Update the file
repo.update_file(
path=contents.path,
message=commit_message,
content=content,
sha=contents.sha,
branch=st.secrets["github_branch"]
)
return True, "Successfully pushed to GitHub!"
except Exception as e:
return False, f"GitHub Error: {str(e)}"
def pull_from_github():
"""
1. Wipes the local 'topics' folder.
2. Downloads a fresh copy from GitHub.
"""
try:
# 1. WIPE LOCAL FOLDER CLEAN
if os.path.exists(TOPICS_DIR):
shutil.rmtree(TOPICS_DIR) # Deletes the folder and everything inside
# Re-create the empty folder
os.makedirs(TOPICS_DIR, exist_ok=True)
# 2. CONNECT TO GITHUB
g = Github(st.secrets["github_token"])
repo = g.get_repo(st.secrets["github_repo"])
# 3. DOWNLOAD EVERYTHING
contents = repo.get_contents(TOPICS_DIR, ref=st.secrets["github_branch"])
count = 0
while contents:
file_content = contents.pop(0)
if file_content.type == "dir":
contents.extend(repo.get_contents(file_content.path, ref=st.secrets["github_branch"]))
else:
# Calculate where to save it locally
# We strip the TOPICS_DIR prefix from the GitHub path to ensure correct nesting
local_path = file_content.path
# Ensure the subfolder exists
os.makedirs(os.path.dirname(local_path), exist_ok=True)
# Write the file
with open(local_path, "wb") as f:
f.write(file_content.decoded_content)
count += 1
return True, f"Clean sync complete! Downloaded {count} files."
except Exception as e:
return False, str(e)
# ==========================================
# π MAIN APP LOGIC
# ==========================================
def get_topics():
if not os.path.exists(TOPICS_DIR): return []
return sorted([d for d in os.listdir(TOPICS_DIR) if os.path.isdir(os.path.join(TOPICS_DIR, d))])
def get_topic_files(topic):
topic_path = os.path.join(TOPICS_DIR, topic)
return sorted([f for f in os.listdir(topic_path) if f.lower().endswith(('.tex', '.pdf'))])
def compile_latex(file_path):
file_name = os.path.basename(file_path)
job_name = os.path.splitext(file_name)[0]
# 1. Use ABSOLUTE PATH for the output directory
# (This prevents "cannot find directory" errors)
abs_temp_dir = os.path.abspath(TEMP_DIR)
try:
process = subprocess.run(
[
"pdflatex",
"-interaction=nonstopmode",
f"-output-directory={abs_temp_dir}", # <--- CHANGE THIS
f"-jobname={job_name}",
file_path
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True
)
# Check for the PDF in the absolute path
pdf_path = os.path.join(abs_temp_dir, f"{job_name}.pdf") # <--- AND THIS
if process.returncode == 0 and os.path.exists(pdf_path):
return pdf_path, None
else:
return None, process.stdout
except Exception as e:
return None, str(e)
# --- SIDEBAR ---
st.sidebar.title(f"Physics Archive ({current_role.title()})")
# ADMIN ONLY: Sync Button
if current_role == 'admin' or current_role == 'viewer':
if st.sidebar.button("π Pull from GitHub"):
with st.spinner("Downloading latest files..."):
success, msg = pull_from_github()
if success:
st.sidebar.success(msg)
st.rerun()
else:
st.sidebar.error(msg)
if st.sidebar.button("Logout", type="secondary"):
st.session_state.user_role = None
st.rerun()
topics = get_topics()
if not topics:
st.sidebar.error("No topics found.")
st.stop()
selected_topic = st.sidebar.selectbox("Select Topic", topics)
st.sidebar.markdown("---")
files = get_topic_files(selected_topic)
if not files:
st.sidebar.warning("No files found.")
st.stop()
selected_file = st.sidebar.radio(f"Documents in {selected_topic}", files)
# --- FILE PATHS ---
# Relative path (for GitHub): "topics/Topic1/File.tex"
rel_path = os.path.join(TOPICS_DIR, selected_topic, selected_file).replace("\\", "/")
# Absolute path (for Python/OS):
abs_path = os.path.abspath(rel_path)
# --- COMPILATION LOGIC (VIEWER & ADMIN) ---
# We track if a file was just saved to trigger a re-compile
if "force_recompile" not in st.session_state:
st.session_state.force_recompile = False
if "last_processed" not in st.session_state:
st.session_state.last_processed = None
# If file changed OR we just saved an edit:
if st.session_state.last_processed != rel_path or st.session_state.force_recompile:
# 1. PDF Files
if selected_file.lower().endswith(".pdf"):
st.session_state.current_pdf = abs_path
st.session_state.compilation_error = None
# 2. LaTeX Files
elif selected_file.lower().endswith(".tex"):
with st.spinner(f"Compiling {selected_file}..."):
pdf, log = compile_latex(abs_path)
st.session_state.current_pdf = pdf
st.session_state.compilation_error = log
st.session_state.last_processed = rel_path
st.session_state.force_recompile = False
# --- TABS ---
# Admin gets "Edit Source", Viewer gets "Source Code"
tab_label = "βοΈ Edit Source" if current_role == 'admin' else "π Source Code"
tab_view, tab_edit = st.tabs(["π Document Viewer", tab_label])
# 1. VIEWER TAB
with tab_view:
if st.session_state.current_pdf and os.path.exists(st.session_state.current_pdf):
col1, col2 = st.columns([6, 1])
with col1: st.success(f"**{selected_file}** loaded.")
with col2:
with open(st.session_state.current_pdf, "rb") as f:
st.download_button("β¬οΈ PDF", f, file_name=selected_file.replace('.tex','.pdf'), mime="application/pdf", type="primary")
st.markdown("---")
pdf_viewer(st.session_state.current_pdf, width=800, height=1000)
elif st.session_state.compilation_error:
st.error("β οΈ Compilation Failed")
with st.expander("Error Log"):
st.code(st.session_state.compilation_error)
# 2. EDIT / SOURCE TAB
with tab_edit:
if selected_file.lower().endswith(".pdf"):
st.info("PDF files cannot be edited directly.")
else:
# Read current content
with open(abs_path, "r", encoding="utf-8") as f:
file_content = f.read()
if current_role == 'admin':
st.warning(f"β οΈ You are editing: {selected_file}")
# The Text Editor
new_content = st.text_area(
"LaTeX Source",
value=file_content,
height=600,
# CRITICAL FIX: Use the file path as the key so the box resets when you switch files
key=abs_path
)
# SAVE BUTTON
if st.button("πΎ Save to GitHub & Re-Render", type="primary"):
if new_content != file_content:
# 1. Save locally (so valid for compilation immediately)
with open(abs_path, "w", encoding="utf-8") as f:
f.write(new_content)
# 2. Push to GitHub
with st.spinner("Pushing to GitHub..."):
success, msg = push_to_github(
rel_path,
new_content,
f"Update {selected_file} via Streamlit Admin"
)
if success:
st.success(msg)
# Trigger re-compile on next run
st.session_state.force_recompile = True
st.rerun()
else:
st.error(msg)
else:
st.info("No changes detected.")
else:
# Viewer Mode (Read Only)
st.caption(f"Path: {rel_path}")
st.code(file_content, language="latex") |