| """ |
| Shared UI components for the Distractor Annotation Tool. |
| Call render_sidebar() at the top of every page. |
| """ |
|
|
| import os |
| import streamlit as st |
|
|
|
|
| def render_sidebar(page_title: str = "") -> str: |
| """ |
| Render the common sidebar. Returns the current annotator name. |
| Uses a unique widget key per page to avoid Streamlit key collisions. |
| """ |
| with st.sidebar: |
| st.markdown("## π― Annotation Tool") |
| st.caption("NLP with DL β MSc Research") |
| st.divider() |
|
|
| |
| current_name = st.session_state.get("annotator_name", "") |
|
|
| if current_name: |
| st.success(f"π€ **{current_name}**") |
| if st.button("βοΈ Change name", use_container_width=True): |
| st.session_state["annotator_name"] = "" |
| st.rerun() |
| else: |
| name = st.text_input( |
| "Your name", |
| placeholder="e.g. Alice", |
| |
| key=f"sidebar_name__{page_title.replace(' ', '_')}", |
| ) |
| if name.strip(): |
| st.session_state["annotator_name"] = name.strip() |
| st.rerun() |
| else: |
| st.warning("β οΈ Enter your name to annotate") |
|
|
| st.divider() |
|
|
| |
| token_ok = bool( |
| os.environ.get("HF_TOKEN") |
| or (st.secrets.get("HF_TOKEN") if hasattr(st, "secrets") else None) |
| ) |
| repo_ok = bool( |
| os.environ.get("ANNOTATIONS_REPO_ID") |
| or (st.secrets.get("ANNOTATIONS_REPO_ID") if hasattr(st, "secrets") else None) |
| ) |
|
|
| if token_ok and repo_ok: |
| st.caption("π Connected to shared repo") |
| else: |
| if not token_ok: |
| st.error("HF_TOKEN missing") |
| if not repo_ok: |
| st.error("ANNOTATIONS_REPO_ID missing") |
|
|
| st.divider() |
| st.caption("π Links") |
| st.markdown("[Base Dataset](https://huggingface.co/datasets/nvidia/CantTalkAboutThis-Topic-Control-Dataset)") |
| st.markdown("[EMNLP Paper](https://aclanthology.org/2024.findings-emnlp.713)") |
| st.markdown("[arXiv:2511.05018](https://arxiv.org/abs/2511.05018)") |
|
|
| return st.session_state.get("annotator_name", "") |
|
|
|
|
| def render_conversation(conversation: list[dict], highlight_turn: str = "") -> None: |
| """Render a conversation as chat bubbles. Highlights matching bot_turn if given.""" |
| for turn in conversation: |
| role = turn.get("role", "user") |
| content = turn.get("content", "") |
| is_highlight = highlight_turn and role == "assistant" and content == highlight_turn |
| with st.chat_message(role): |
| if is_highlight: |
| st.markdown(f"**β¬οΈ Injection point:**\n\n{content}") |
| else: |
| st.write(content) |
|
|
|
|
| def render_distractor_multiturn(distractor: dict, idx: int) -> None: |
| """Render a single multi-turn distractor entry.""" |
| turns = distractor.get("turns", []) |
| st.markdown(f"**Subject:** {distractor.get('off_topic_subject', 'β')}") |
| st.markdown(f"**Tactic:** `{distractor.get('tactic_used', 'β')}`") |
| st.markdown(f"**Injected after:** _{distractor.get('bot_turn', 'β')[:80]}..._") |
| st.markdown(f"**Turns:** {len(turns)}") |
| with st.expander("Show turns"): |
| for t in turns: |
| with st.chat_message(t.get("role", "user")): |
| st.write(t.get("content", "")) |
|
|