import streamlit as st import traceback import random from src.template_loader import ( get_branches, get_areas, get_template_files, load_template_functions, get_source_code ) from src.storage import save_review # Page Configuration st.set_page_config(layout="wide", page_title="EngChain Annotator") # Custom CSS for Styling # This forces the primary buttons to have a specific look (optional but helps visibility) st.markdown(""" """, unsafe_allow_html=True) # Helper Function: Build the Queue def build_review_queue(branch): """ Scans the entire branch and creates a list of all templates to review. Returns: List of dicts {'branch', 'area', 'file', 'name', 'func'} """ queue = [] areas = get_areas(branch) progress_bar = st.progress(0) status_text = st.empty() total_steps = len(areas) for i, area in enumerate(areas): status_text.text(f"Loading area: {area}...") files = get_template_files(branch, area) for file in files: # Load all functions from this file try: funcs = load_template_functions(branch, area, file) for func_name, func_obj in funcs: queue.append({ "branch": branch, "area": area, "file": file, "name": func_name, "func": func_obj }) except Exception as e: print(f"Error loading {file}: {e}") progress_bar.progress((i + 1) / total_steps) status_text.empty() progress_bar.empty() return queue # Session State Initialization if "app_mode" not in st.session_state: st.session_state["app_mode"] = "landing" # landing, review, done if "review_queue" not in st.session_state: st.session_state["review_queue"] = [] if "current_index" not in st.session_state: st.session_state["current_index"] = 0 if "annotator_name" not in st.session_state: st.session_state["annotator_name"] = "" if "current_branch" not in st.session_state: st.session_state["current_branch"] = "" if "review_submitted" not in st.session_state: st.session_state["review_submitted"] = False # ========================================== # VIEW 1: LANDING PAGE # ========================================== if st.session_state["app_mode"] == "landing": st.title("🛡️ EngChain Verification Portal") st.markdown(""" ### Welcome, Expert Annotator! Thank you for contributing to **EngChain**. Your task is to verify the correctness and quality of our symbolic engineering templates. **Instructions:** 1. Select your Engineering Domain. 2. Enter your Name/ID. 3. You will be guided through templates one-by-one. 4. For each template, check the **Code** and the **Generated Trace**. 5. Rate it, Approve/Reject it, and click **Submit**. """) st.markdown("") with st.form("onboarding_form"): st.subheader("Annotator Details") col1, col2 = st.columns(2) with col1: branches = get_branches() branch_input = st.selectbox("Select Your Domain", branches) with col2: name_input = st.text_input("Enter Your Name / ID") # Button styling: type="primary" gives it emphasis (color depends on theme, usually red/blue) submitted = st.form_submit_button("Start Annotation Session", type="primary") if submitted: if not name_input.strip(): st.error("Please enter your name to proceed.") else: st.session_state["annotator_name"] = name_input st.session_state["current_branch"] = branch_input # Build the queue with st.spinner(f"Gathering templates for {branch_input}..."): queue = build_review_queue(branch_input) st.session_state["review_queue"] = queue st.session_state["current_index"] = 0 st.session_state["app_mode"] = "review" st.rerun() # ========================================== # VIEW 2: REVIEW WORKSPACE # ========================================== elif st.session_state["app_mode"] == "review": queue = st.session_state["review_queue"] idx = st.session_state["current_index"] # Check if we are done if idx >= len(queue): st.session_state["app_mode"] = "done" st.rerun() current_item = queue[idx] # Sidebar Info st.sidebar.title("Progress") st.sidebar.progress((idx) / len(queue)) st.sidebar.write(f"Template: {idx + 1} / {len(queue)}") st.sidebar.markdown("") st.sidebar.subheader("Current Context") # Distinct styling for keys and values st.sidebar.markdown("**📂 Area:**") st.sidebar.info(current_item['area']) st.sidebar.markdown("**📄 File:**") st.sidebar.info(current_item['file']) st.sidebar.markdown("**🧩 Function:**") st.sidebar.info(current_item['name']) # Main Content st.title(f"Reviewing: {current_item['name']}") # Tabs for Inspection tab1, tab2 = st.tabs(["Source Code", "Generated Trace"]) with tab1: code_text = get_source_code(current_item['func']) st.code(code_text, language="python", line_numbers=True) with tab2: col_gen, _ = st.columns([1, 4]) # Renamed button for clarity if col_gen.button("Generate New Random Instance"): pass # Rerun trigger to get new random numbers try: question, solution = current_item['func']() st.markdown("#### Question") st.info(question) st.markdown("#### Solution Trace") st.success(solution) except Exception as e: st.error("Template Execution Failed") st.code(traceback.format_exc()) st.markdown("") # Scoring Form st.subheader("Audit Decision") # Use a container so we can disable the form after submission with st.container(): # Check if already submitted for this specific template is_submitted = st.session_state.get("review_submitted", False) if not is_submitted: # FORM STATE with st.form("audit_form"): c1, c2, c3 = st.columns(3) phys = c1.slider("Physical Plausibility", 1, 5, 5) math = c2.slider("Mathematical Correctness", 1, 5, 5) ped = c3.slider("Pedagogical Clarity", 1, 5, 5) decision = st.radio("Certification:", ["Approve", "Reject"], horizontal=True) feedback = st.text_area("Feedback (Required for Rejection)", placeholder="Explain any errors found...") # Removed emoji from button submit_review = st.form_submit_button("Submit Review") if submit_review: if decision == "Reject" and not feedback.strip(): st.error("Feedback is required for Rejection.") else: # Save to disk save_review( st.session_state["annotator_name"], current_item["branch"], current_item["area"], current_item["name"], [phys, math, ped], decision, feedback ) st.success("Review Saved!") st.session_state["review_submitted"] = True st.rerun() else: # POST-SUBMISSION STATE (Show Next Button) st.success("Review recorded for this template.") # Removed emoji, kept primary type for visibility if st.button("Proceed to Next Template", type="primary"): st.session_state["current_index"] += 1 st.session_state["review_submitted"] = False st.rerun() # ========================================== # VIEW 3: COMPLETION PAGE # ========================================== elif st.session_state["app_mode"] == "done": # Removed balloons() st.title("Session Complete") st.success(f""" **Thank you, {st.session_state['annotator_name']}!** You have successfully reviewed all **{len(st.session_state['review_queue'])}** templates in the **{st.session_state['current_branch']}** domain. """) st.info("Your reviews have been saved securely. You may now close this tab.") if st.button("Start New Session"): st.session_state.clear() st.rerun()