File size: 13,249 Bytes
c43652c
 
013edfc
 
c43652c
 
 
 
 
 
 
 
 
 
 
 
 
3c402e6
52058f8
c43652c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b304a75
 
4245ba9
b304a75
 
92c1ba9
 
 
 
c43652c
 
 
 
 
 
5e721ae
 
 
b304a75
52058f8
5e721ae
92c1ba9
 
 
8ce15da
 
92c1ba9
 
c43652c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92c1ba9
c43652c
8ce15da
c43652c
 
9b0f98b
92c1ba9
 
 
c43652c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68d3681
c43652c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5e721ae
 
 
 
ade959f
c43652c
 
92c1ba9
c43652c
 
b304a75
 
 
4245ba9
 
b304a75
9f4c7d5
36758e4
 
 
92c1ba9
 
 
c43652c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
be58add
 
 
fb0c588
c43652c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
302
303
304
305
306
307
308
309
310
import json
import os
from dataclasses import dataclass, field
from typing import List, Optional, Dict
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', 'neutral', 'moderate priority', 'high priority']
default_labels = agreement_labels

function_choices = ['Broadening Discussion', 'Improving Comment Quality', 'Content Correction', 'Keeping Discussion on Topic', 'Organizing Discussion', 'Policing', 'Resolving Site Use Issues', 'Social Functions', 'Other (please specify)']
default_choices = function_choices


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="container", title="**Moderation Function**", children=[
        Field(name="mod_function", type="multiselect",
                     title="**What type of moderation function is needed here?** (Multiple selection possible)"),
    ]),

    
    #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': 3,
                              'text': None,
                              'textarea': None,
                              'checkbox': False,
                              'radio': False,
                              'select_slider': 0,
                              'multiselect': None}
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,
                                                                 help=f.help)
            case 'slider':
                st.session_state.data_inputs[f.name] = st.slider(f.title,
                                                                 min_value=0, max_value=6, 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=[0, 25, 50, 75, 100],
                                                                        format_func=lambda x: labels[x // 25],
                                                                        key=key,
                                                                        value=value, help=f.help)
            case 'multiselect':
                choices = default_choices if not f.other_params.get('choices') else f.other_params.get('choices')
                st.session_state.data_inputs[f.name] = st.multiselect(f.title,
                                                                options = choices,
                                                                key=key,
                                                                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(
    """<style>
div[data-testid="stMarkdownContainer"] > p {
    font-size: 1rem;
}
    </style>
    """, unsafe_allow_html=True)

st.markdown(
    """<details open>
  <summary>Annotation Guidelines</summary>
  
  some guidelines here
</details>
    """, 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(
    """
    <script>
    const inputs = window.parent.document.querySelectorAll('input');
    inputs.forEach(input => {
        input.addEventListener('keydown', function(event) {
            if (event.key === 'Enter') {
                event.preventDefault();
            }
        });
    });
    </script>
    """,
    height=0
)
st.markdown(
    """<style>
    div[data-testid="InputInstructions"] {
        visibility: hidden;
    }
    </style>""", unsafe_allow_html=True
)