File size: 11,753 Bytes
62ae541
2381369
3fb7cec
 
 
11cf5bf
5b4df28
62ae541
11cf5bf
62ae541
11cf5bf
669a454
 
6fa73ee
c642435
669a454
6fa73ee
c462b54
 
669a454
 
c462b54
 
669a454
6fa73ee
 
 
 
 
 
 
 
 
 
 
 
 
 
669a454
75a63be
de91e03
 
669a454
 
 
ff13221
669a454
 
 
adef5d8
669a454
 
 
 
 
 
de91e03
62ae541
92b0b5a
de91e03
 
ff13221
11cf5bf
 
 
de91e03
62ae541
 
b7fdcd4
62ae541
 
 
ff13221
 
421f188
 
c0bbc73
021fcaa
 
421f188
ff13221
62ae541
 
de91e03
 
 
 
62ae541
de91e03
11cf5bf
 
 
 
 
 
de91e03
11cf5bf
de91e03
62ae541
 
de91e03
 
021fcaa
ff13221
669a454
 
ff13221
669a454
 
18fb6b4
 
 
 
 
669a454
18fb6b4
ff13221
 
 
 
424cdcc
 
9b945df
021fcaa
 
ff13221
11cf5bf
ff13221
 
3b89ee2
ff13221
62ae541
3b89ee2
11cf5bf
 
 
de91e03
 
 
 
3b89ee2
62ae541
11cf5bf
 
 
 
 
ff13221
59f7966
ff13221
669a454
ff13221
 
c0bbc73
 
ff13221
53a8836
9b437ed
8f44ef8
9b945df
33d969e
 
 
9b437ed
 
4f1fb9d
59f7966
84d5743
 
59f7966
9b437ed
e899ce6
 
9b437ed
e899ce6
9b945df
 
33d969e
 
59f7966
 
 
9b945df
33d969e
 
59f7966
 
 
ff13221
de91e03
 
ff13221
de91e03
 
ff13221
11cf5bf
669a454
 
62ae541
11cf5bf
de91e03
 
11cf5bf
9ccbcb9
11cf5bf
 
62ae541
3b89ee2
00b3dec
62ae541
669a454
 
7cfff71
3ea1a7a
6fa73ee
 
 
 
 
 
 
 
c642435
3ea1a7a
6fa73ee
 
 
251bf3b
18fb6b4
7fe87c5
1bb5f79
6fa73ee
 
5f4e0dd
6fa73ee
 
 
 
 
 
1bb5f79
6fa73ee
1384878
7fe87c5
 
2c10d47
2038e95
b2fd8ac
7fe87c5
 
6fa73ee
 
 
 
773e1c0
5451873
 
021fcaa
 
812f9b5
6fa73ee
 
 
 
 
 
 
62ae541
ff13221
812f9b5
ff13221
4165e17
ff13221
 
4165e17
 
 
 
 
 
 
 
3b89ee2
18fb6b4
3b89ee2
1bb5f79
 
 
 
 
 
 
 
 
 
 
 
62ae541
3b89ee2
 
 
62ae541
 
 
3b89ee2
 
 
62ae541
 
 
 
021fcaa
3b89ee2
62ae541
 
 
3b89ee2
 
62ae541
 
1bb5f79
62ae541
9ccbcb9
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
311
312
313
314
315
316
317
318
319
320
321
322
323
324
import gradio as gr
import json
import os
import csv
import pandas as pd
from datasets import load_dataset, Dataset

# Configuration
DATASET_NAME = "anna-tch/generation-results"
PROGRESS_FILE = "progress.csv"
HF_TOKEN = os.getenv("HF_TOKEN")
DEFAULT_ANNOTATION_COL = "manual_annotation"  # Renamed for clarity

def login(username, password, state):
    if password == os.getenv("LOGIN_PASSWORD"):
        # Store username and annotation column in state
        state["username"] = username
        new_annotation_col = f"{DEFAULT_ANNOTATION_COL}.{username}"
        state["annotation_col"] = new_annotation_col
        
        # Load data AFTER login with the correct annotation column
        data = load_data(state["annotation_col"])
        state.update(data) # Merge data into the state
        
        return (
            gr.update(visible=False),
            gr.update(visible=True),
            gr.update(visible=False),
            state
        )
    else:
        return (
            gr.update(visible=True),
            gr.update(visible=False),
            gr.update(value="Incorrect password", visible=True),
            state
        )

def load_data(annotation_col):  # Accept annotation_col as a parameter
    dataset = load_dataset(DATASET_NAME, token=HF_TOKEN)['train']
    df = dataset.to_pandas().set_index('comment_id', drop=False)
    
    # Create user-specific annotation column if missing
    if annotation_col not in df.columns:
        df[annotation_col] = ""
    
    # Identify adapter columns dynamically (exclude annotation cols)
    adapter_columns = [
        col for col in df.columns 
        if col not in ['comment_id','inputs', 'mappings',annotation_col] 
        and not col.startswith(DEFAULT_ANNOTATION_COL)
    ]
    
    # Track annotated/unannotated IDs
    annotated_ids = df[df[annotation_col] != ""].index.tolist()
    unannotated_ids = df[df[annotation_col] == ""].index.tolist()
    
    return {
        "annotation_col": annotation_col,
        "original_dataset": dataset,
        "dataset_df": df,
        "adapter_columns": adapter_columns,
        "annotated_ids": annotated_ids,
        "unannotated_ids": unannotated_ids,
        "current_view": "unannotated",
        "current_id_index": 0,
        "annotations": {}
    }

def save_progress(annotation_data):
    with open(PROGRESS_FILE, 'a', newline='') as f:
        writer = csv.writer(f)
        row = [annotation_data['comment_id']]
        for adapter in annotation_data['scores']:
            row.extend([adapter, 
                        annotation_data['scores'][adapter]['grammar'], 
                        annotation_data['scores'][adapter]['coherence'],
                        annotation_data['scores'][adapter]['hallucination'],
                        annotation_data['scores'][adapter]['preferred_text']
                          ]) 
        writer.writerow(row)

def update_dataset(state):
    updated_dataset = Dataset.from_pandas(
        state["dataset_df"].reset_index(drop=True),
    )
    updated_dataset.push_to_hub(DATASET_NAME, token=HF_TOKEN)

def get_current_comment(state):
    if state["current_view"] == "annotated":
        ids = state["annotated_ids"]
    else:
        ids = state["unannotated_ids"]
    
    if not ids:
        return None, None
    comment_id = ids[state["current_id_index"]]
    return state["dataset_df"].loc[comment_id], comment_id

def update_display(state):
    example, comment_id = get_current_comment(state)
    if not comment_id:
        return [gr.update(value="No examples!")] + [gr.update(visible=False)] * len(state["adapter_columns"]) * 5 + [gr.update(value="Complete!"), state]
    
    # Use annotation column from state
    annotation_col = state["annotation_col"]
    existing_annotation = {}
    if example[annotation_col]:
        existing_annotation = json.loads(example[annotation_col])


    # Retrieve the original inputs
    input_text = example.get("inputs", "No input available")
    updates = [gr.update(value=input_text)]  # First element is the inputs textbox update
    

    for adapter in state["adapter_columns"]:
        text = example.get(adapter, "")
        scores = existing_annotation.get(adapter, {}) if existing_annotation else {}
        updates.extend([
            gr.update(value=text, visible=True),
            gr.update(value=scores.get("grammar"), visible=True),
            gr.update(value=scores.get("coherence"), visible=True),
            gr.update(value=scores.get("hallucination"), visible=True),
            gr.update(value=scores.get("preferred_text"), visible=True)
        ])
    
    return updates + [
        gr.update(value=f"Example {state['current_id_index'] + 1} of {len(state[state['current_view'] + '_ids'])} ({state['current_view']})"),
        state
    ]

def navigate(direction, state):
    ids = state[state["current_view"] + "_ids"]
    max_index = len(ids) - 1
    
    if direction == "next" and state["current_id_index"] < max_index:
        state["current_id_index"] += 1
    elif direction == "prev" and state["current_id_index"] > 0:
        state["current_id_index"] -= 1
    return update_display(state)

def switch_view(target_view, state):
    state["current_view"] = target_view
    state["current_id_index"] = 0
    return update_display(state)

def submit(*args):

    state = args[-1]
    annotation_col = state["annotation_col"]
    comment_id = state[state["current_view"] + "_ids"][state["current_id_index"]]
    
    # Each adapter has 9 components: text, grammar_title, grammar, coherence_title, coherence, separator, hallucination_title, hallucination, separator
    components_per_adapter = 9
    adapter_scores = {}


    gramar_scores = args[:len(state["adapter_columns"])]
    coherence_scores = args[len(state["adapter_columns"]):len(state["adapter_columns"])*2]
    hallucination_scores = args[len(state["adapter_columns"])*2:len(state["adapter_columns"])*3]
    preferred_texts = args[len(state["adapter_columns"])*3:]



    
    for i in range(len(state["adapter_columns"])):
        #base_idx
        print(i)
        try:
            grammar = gramar_scores[i]
            print(state["adapter_columns"][i])
            print("Grammar: ", grammar)
            coherence = coherence_scores[i]
            print("Coherence: ", coherence)
            hallucination = hallucination_scores[i]
            print("Hallucination: ", hallucination)
            preferred_text = preferred_texts[i]
            print("Preferred Text: ", preferred_text)
            
            adapter_scores[state["adapter_columns"][i]] = {
                "grammar": int(grammar) if grammar else None,
                "coherence": int(coherence) if coherence else None,
                "hallucination": int(hallucination) if hallucination else None,
                "preferred_text": preferred_text
            }
        except IndexError:
            print("Error: Not all scores provided") 
    
    annotation = {
        "comment_id": comment_id,
        "scores": adapter_scores
    }
    
    # Update state
    state["annotations"][comment_id] = annotation
    state["dataset_df"].at[comment_id, annotation_col] = json.dumps(adapter_scores)
    #state["dataset_df"].at[comment_id, ANNOTATION_COL] = json.dumps(adapter_scores)
    
    # Update lists if new annotation
    if comment_id in state["unannotated_ids"]:
        state["unannotated_ids"].remove(comment_id)
        state["annotated_ids"].append(comment_id)
    
    save_progress(annotation)
    update_dataset(state)
    
    return navigate("next", state)

with gr.Blocks() as app:

    # Initialize state as an empty dict
    state = gr.State(load_data(DEFAULT_ANNOTATION_COL))
    
    # Login interface
    with gr.Row(visible=True, elem_id="login_row") as login_row:
        with gr.Column():
            gr.Markdown("## Login")
            username_input = gr.Textbox(label="Username")
            password_input = gr.Textbox(label="Password", type="password")
            login_button = gr.Button("Login")
            login_error = gr.Markdown(visible=False)


    # Main interface
    with gr.Column(visible=False, elem_id="main_interface") as main_interface:
        gr.Markdown("## Text Annotation Tool")
        print("===>", state.value["annotation_col"])

        
        
        with gr.Row():
            # Sidebar Column
            with gr.Column(scale=1.5, min_width=300, elem_classes="sidebar"):
                gr.Markdown("### Navigation")
                with gr.Column(elem_classes="nav-buttons"):
                    annotated_btn = gr.Button("Annotated", variant="primary")
                    unannotated_btn = gr.Button("Unannotated", variant="primary")
                    gr.Markdown("---")
                    counter = gr.Markdown()
            
            # Main Content Column
            with gr.Column(scale=5, min_width=600, variant="compact"):

                # Add a textbox for the input at the top
                with gr.Accordion('Original Input', open=False):
                    input_display = gr.Markdown()
                    #input_display = gr.Textbox(interactive=False)
                gr.Markdown("---")

                adapter_components = []
                for adapter in state.value["adapter_columns"]:
                    with gr.Row():
                        adapter_components.extend([
                            gr.Textbox(label=f"{adapter}", visible=False, interactive=False, scale=2, lines=9),
                            gr.Radio(choices=[1, 2, 3, 4, 5], label=f"Grammar", visible=False, scale=0.3, min_width=100),
                            gr.Radio(choices=[1, 2, 3, 4, 5], label=f"Coherence", visible=False, scale=0.3, min_width=100),
                            gr.Radio(choices=[1, 2, 3, 4, 5], label=f"Hallucination", visible=False, scale=0.3, min_width=100),
                            gr.Checkbox(label="Preferred", visible=False, scale=0.2)

                        ])
                    gr.Markdown("---")
                
                with gr.Row():
                    prev_btn = gr.Button("Previous", variant="secondary")
                    next_btn = gr.Button("Next", variant="secondary")
                    submit_btn = gr.Button("Submit", variant="primary")

    app.css = """
    .sidebar { background: #f8f9fa; padding: 10px; border-right: 1px solid #dee2e6; height: 100vh; }
    .nav-buttons { background: #f0f0f0; padding: 20px; border-radius: 10px; }
    #login_row { padding: 20px; }
    """

    # Login handler
    login_button.click(
        login,
        inputs=[username_input, password_input, state],
        outputs=[login_row, main_interface, login_error, state]
    )


    navigation_inputs = [state]
    navigation_outputs =  [input_display] + adapter_components + [counter, state]
    
    annotated_btn.click(
        lambda s: switch_view("annotated", s),
        inputs=[state],
        outputs=navigation_outputs
    )
    
    unannotated_btn.click(
        lambda s: switch_view("unannotated", s),
        inputs=[state],
        outputs=navigation_outputs
    )
    
    prev_btn.click(
        lambda s: navigate("prev", s),
        inputs=navigation_inputs,
        outputs=navigation_outputs
    )
    
    next_btn.click(
        lambda s: navigate("next", s),
        inputs=navigation_inputs,
        outputs=navigation_outputs
    )
    
    submit_btn.click(
        submit,
        inputs=adapter_components[1::5] + adapter_components[2::5] + adapter_components[3::5] + adapter_components[4::5] + [state],
        outputs=navigation_outputs
    )
    
    app.load(
        lambda: update_display(state.value),
        outputs=navigation_outputs
    )


if __name__ == "__main__":
    app.launch(share=True)