import json import os from dataclasses import dataclass from typing import List, Optional from PIL import Image import pandas as pd import streamlit as st from huggingface_hub import HfFileSystem @dataclass class Field: type: str title: str name: str = None help: Optional[str] = None children: List['Field'] = None other_params: Optional[Dict[str, object]] = field(default_factory=lambda: {}) # Function to get user ID from URL def get_user_id_from_url(): user_id = st.query_params.get("user_id", "") return user_id HF_TOKEN = os.environ.get("HF_TOKEN_WRITE") print("is none?", HF_TOKEN is None) hf_fs = HfFileSystem(token=HF_TOKEN) input_repo_path = 'datasets/emvecchi/annotate-pilot' output_repo_path = 'datasets/emvecchi/annotate-pilot' to_annotate_file_name = 'to_annotate.csv' # CSV file to annotate COLS_TO_SAVE = ['comment_id'] agreement_labels = ['strongly disagree', 'disagree', 'neither agree no disagree', 'agree', 'strongly agree'] quality_labels = ['very poor', 'poor', 'acceptable', 'good', 'very good'] priority_labels = ['not a priority', 'low priority', 'somewhat a priority', 'neutral', 'moderate priority', 'high priority', 'essential priority'] default_labels = agreement_labels fields: List[Field] = [ Field(name="topic", type="input_col", title="**Topic:**"), Field(name="parent_comment", type="input_col", title="**Preceeding Comment:**"), Field(name="comment", type="input_col", title="**Comment:**"), Field(name="image_name", type="input_col", title="**Visualization of high contributing properties:**"), Field(type="container", title="", children=[ Field(name="to_moderate", type="radio", title="**Moderator Intervention**: Do feel this comment/discussion would benefit from moderator intervention?"), Field(name="actions_clear", type="select_slider", title="**Priority**: With what level of priority would you need to interact with this comment?", other_params={'labels': priority_labels}), ]), #Field(type="expander", # title="Expand and fill-out this section if you see **issues in the original comment**", # children=[ # Field(name="issues_in_comment", type="slider", # title="Do **you see some issues** in the original comment?"), # Field(name="moderator_spotted", type="slider", # title="Based on the reply, has the **moderator spotted the issues** in the original comment?"), # Field(name="reply_addresses_issues", type="slider", # title="How well does the reply **address those issues**?") # ]), #Field(type="container", title="**Score the following properties of the moderator comment?**", children=[ # Field(name="neutrality", type="slider", title="Neutrality", # help='Remain Neutral on the topic and on the Comment Substance and Commenter’s Viewpoint. The reply shouldn’t give away the opinion of the moderator on the topic or comment. '), # # FieldDict(name="attitude", type="slider", title="Attitude", help=''), # Field(name="clarity", type="slider", title="Clarity", # help="Plain language, simple, clear, avoid overwhelming the user e.g. too many questions"), # Field(name="curiosity", type="slider", title="Curiosity", # help="Moderators should model a spirit of inquiry and a desire to learn from and understand commenter’s experience and views. Try to be interested in the bases upon which each commenter stakes his or her claims and the lines of reasoning that has led each commenter to those particular conclusions."), # # TODO # Field(name="bias", type="slider", title="Bias", # help="Does the reply show some biases towards the commenter? Are there stereotypes or prejudices?"), # Field(name="encouraging", type="slider", title="Encouraging", # help="Welcoming, encouraging and acknowledging. Avoid Evaluative and/or Condescending Responses"), #]), # Field(name="other_comments", type="text", title="Further comments: free text"), ] INPUT_FIELD_DEFAULT_VALUES = {'slider': 0, 'text': None, 'textarea': None, 'checkbox': False, 'radio': None, 'select_slider': 0} SHOW_HELP_ICON = False def read_data(_path): with hf_fs.open(input_repo_path + '/' + _path) as f: return pd.read_csv(f) def read_saved_data(): _path = get_path() if hf_fs.exists(output_repo_path + '/' + _path): with hf_fs.open(output_repo_path + '/' + _path) as f: try: return json.load(f) except json.JSONDecodeError as e: print(e) return None # Write a remote file def save_data(data): hf_fs.mkdir(f"{output_repo_path}/{data['user_id']}") with hf_fs.open(f"{output_repo_path}/{get_path()}", "w") as f: f.write(json.dumps(data)) def get_path(): return f"{st.session_state.user_id}/{st.session_state.current_index}.json" def display_image(image_path): with hf_fs.open(image_path) as f: img = Image.open(f) st.image(img, caption='10 most contributing properties', use_column_width=True) #################################### Streamlit App #################################### # Function to navigate rows def navigate(index_change): st.session_state.current_index += index_change print(st.session_state.current_index) # https://discuss.streamlit.io/t/click-twice-on-button-for-changing-state/45633/2 st.rerun() def show_field(f: Field, index: int): if f.type not in INPUT_FIELD_DEFAULT_VALUES.keys(): match f.type: case 'input_col': st.write(f.title) if f.name == 'image_name': st.write(f.title) image_name = st.session_state.data.iloc[index][f.name] if image_name: # Ensure the image name is not empty image_path = os.path.join(input_repo_path, 'images', image_name) display_image(image_path) else: st.write(st.session_state.data.iloc[index][f.name]) case 'markdown': st.markdown(f.title) case 'expander' | 'container': with (st.expander(f.title) if f.type == 'expander' else st.container(border=True)): if f.type == 'container': st.markdown(f.title) for child in f.children: show_field(child, index) else: key = f.name + str(index) value = st.session_state.default_values[f.name] = data_collected[f.name] if data_collected else \ INPUT_FIELD_DEFAULT_VALUES[f.type] if not SHOW_HELP_ICON: f.title = f'**{f.title}**\n\n{f.help}' if f.help else f.title f.help = None match f.type: case 'checkbox': st.session_state.data_inputs[f.name] = st.checkbox(f.title, key=key, value=value, help=f.help) case 'radio': st.session_state.data_inputs[f.name] = st.radio(f.title, ["yes","no","other"], key=key, #value=value, help=f.help) case 'slider': st.session_state.data_inputs[f.name] = st.slider(f.title, min_value=1, max_value=7, step=1, key=key, value=value, help=f.help) case 'select_slider': labels = default_labels if not f.other_params.get('labels') else f.other_params.get('labels') st.session_state.data_inputs[f.name] = st.select_slider(f.title, options=labels, #format_func=lambda x: labels[x // 25], key=key, value=labels[3], help=f.help) case 'text': st.session_state.data_inputs[f.name] = st.text_input(f.title, key=key, value=value) case 'textarea': st.session_state.data_inputs[f.name] = st.text_area(f.title, key=key, value=value) # st.set_page_config(layout='wide') # Title of the app st.title("Moderation Prediction") st.markdown( """ """, unsafe_allow_html=True) st.markdown( """
Annotation Guidelines some guidelines here
""", unsafe_allow_html=True) # Load the data to annotate if 'data' not in st.session_state: st.session_state.data = read_data(to_annotate_file_name) # Initialize the current index if 'current_index' not in st.session_state: st.session_state.current_index = -1 if st.session_state.current_index == -1: user_id_from_url = get_user_id_from_url() if user_id_from_url: st.session_state.user_id = user_id_from_url navigate(1) else: st.session_state.user_id = st.text_input('Please enter your user ID to proceed', value=user_id_from_url) if st.button("Next"): navigate(1) elif st.session_state.current_index < len(st.session_state.data): st.write(f"username is {st.session_state.user_id}") # Creating the form with st.form("feedback_form"): index = st.session_state.current_index data_collected = read_saved_data() st.session_state.default_values = {} st.session_state.data_inputs = {} for field in fields: if field.name not in st.session_state.data.columns: # Field doesn't exist in input dataframe, add it with a default value st.session_state.data_inputs[field.name] = None show_field(field, index) submitted = st.form_submit_button("Submit") if submitted: with st.spinner(text="saving"): save_data({ 'user_id': st.session_state.user_id, 'index': st.session_state.current_index, **st.session_state.data.iloc[index][COLS_TO_SAVE].to_dict(), **st.session_state.data_inputs }) st.success("Feedback submitted successfully!") navigate(1) else: st.write("Finished all data points!") # Navigation buttons if st.session_state.current_index > 0: if st.button("Previous"): with st.spinner(text="in progress"): navigate(-1) if 0 <= st.session_state.current_index < len(st.session_state.data): st.write(f"Page {st.session_state.current_index + 1} out of {len(st.session_state.data)}") # disable text input enter to submit # https://discuss.streamlit.io/t/text-input-how-to-disable-press-enter-to-apply/14457/6 import streamlit.components.v1 as components components.html( """ """, height=0 ) st.markdown( """""", unsafe_allow_html=True )