Kamyar-zeinalipour commited on
Commit
cab9a43
Β·
verified Β·
1 Parent(s): 9aaf42f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +178 -260
app.py CHANGED
@@ -1,337 +1,255 @@
1
- # paragraph_annotation_tool.py NEW ui
2
  """
3
- Paragraph-level annotation tool with per-model comments
4
- ────────────────────────────────────────────────────────────────────────────
5
- β€’ Upload a CSV containing at least:
6
- Content_Paragraph,
7
- <model>_prompt1, <model>_prompt2 … for each model
8
- β€’ Enter your annotator name and click β€œStart / Resume”.
9
- β€’ Rate each prompt A / B / C, optionally leave a comment for every model,
10
- navigate Back / Next, download the annotated CSV.
11
-
12
- New in this version
13
- ───────────────────
14
- β€’ One **comment textbox per model** (shown next to the model’s two ratings).
15
- β€’ Comments are stored in the CSV under columns named ``comment_<model>``.
16
- β€’ Blank comments are saved as the literal string ``"no comment"``.
17
  """
18
  from __future__ import annotations
19
- import gradio as gr, pandas as pd
20
- import random, time, os, shutil, uuid
 
 
21
  from typing import List
22
 
23
- # ─── CONFIG ───────────────────────────────────────────────────────────────
24
- MAX_MODELS = 10 # pre-allocate up to this many models
25
- CONTENT_COL = "Content_Paragraph"
26
- PROMPT1_SUFFIX = "_prompt1"
27
- PROMPT2_SUFFIX = "_prompt2"
28
- COMMENT_PREFIX = "comment_" # <COMMENT_PREFIX><model>
29
- PERM_COL = "perm_models"
30
- RATING_OPTS = ["A", "B", "C"]
31
 
32
- # ─── GLOBALS (filled after CSV load) ───────────────────────────────────────
33
  df: pd.DataFrame | None = None
34
- csv_path: str | None = None
35
- models: List[str] = []
36
- TOTAL = 0
37
- annotator = ""
38
  current_start: float | None = None
39
 
40
- # ─── CSV HELPERS ───────────────────────────────────────────────────────────
41
-
42
- def load_csv(path: str):
43
- """Read CSV, discover model columns, add helper columns if needed."""
44
- global df, models, TOTAL, csv_path
45
- csv_path = path
46
- df = pd.read_csv(csv_path, keep_default_na=False)
47
- TOTAL = len(df)
48
-
49
- models.clear()
50
- for col in df.columns:
51
- if col.endswith(PROMPT1_SUFFIX) and not col.startswith("rating_"):
52
- m = col[: -len(PROMPT1_SUFFIX)]
53
  if f"{m}{PROMPT2_SUFFIX}" not in df.columns:
54
- raise ValueError(f"Found '{col}' but no '{m}{PROMPT2_SUFFIX}'")
55
  models.append(m)
56
-
57
  if not models:
58
- raise ValueError(f"No '*{PROMPT1_SUFFIX}' columns found")
59
  if len(models) > MAX_MODELS:
60
- raise ValueError(
61
- f"CSV has {len(models)} models but MAX_MODELS is {MAX_MODELS}")
62
 
63
- # helper columns
 
 
64
  if PERM_COL not in df.columns:
65
  df[PERM_COL] = ""
 
66
  for m in models:
67
- # rating columns per prompt
68
  for p in ("prompt1", "prompt2"):
69
- rc = f"rating_{m}__{p}"
70
- if rc not in df.columns:
71
- df[rc] = ""
72
- # NEW β†’ comment column per model
73
- cc = f"{COMMENT_PREFIX}{m}"
74
- if cc not in df.columns:
75
- df[cc] = "no comment" # default value
76
 
77
  for col in ("annotator", "annotation_time"):
78
  if col not in df.columns:
79
  df[col] = "" if col == "annotator" else 0.0
80
 
81
 
82
- # ─── BOOK-KEEPING ──────────────────────────────────────────────────────────
83
-
84
  def first_incomplete() -> int:
 
85
  for i, row in df.iterrows():
86
  for m in models:
87
- if (
88
- row[f"rating_{m}__prompt1"] == "" or
89
- row[f"rating_{m}__prompt2"] == ""
90
- ):
91
  return i
92
  return 0
93
 
94
 
95
  def get_perm(idx: int) -> List[str]:
96
- cell = str(df.at[idx, PERM_COL])
97
- if not cell:
98
- seq = models.copy()
99
- random.shuffle(seq)
100
- df.at[idx, PERM_COL] = "|".join(seq)
101
- df.to_csv(csv_path, index=False)
102
- return df.at[idx, PERM_COL].split("|")
103
-
 
 
 
104
 
105
  def build_row(idx: int):
106
- """Return fixed-length tuple of widget values for example *idx*."""
107
- global current_start
108
  row = df.loc[idx]
109
  order = get_perm(idx)
110
 
111
- outs, rates, comms = [], [], []
112
  for m in order:
113
- outs.append(row[f"{m}{PROMPT1_SUFFIX}"])
114
- outs.append(row[f"{m}{PROMPT2_SUFFIX}"])
115
- rates.append(row[f"rating_{m}__prompt1"] or None)
116
- rates.append(row[f"rating_{m}__prompt2"] or None)
117
- val = row[f"{COMMENT_PREFIX}{m}"]
118
- comms.append("" if val == "no comment" else val)
119
-
120
-
121
- # pad up to MAX_MODELS
122
- outs += [""] * (MAX_MODELS*2 - len(outs))
123
- rates += [None]* (MAX_MODELS*2 - len(rates))
124
- comms += ["" ] * (MAX_MODELS - len(comms))
125
-
126
- ready = all(r in RATING_OPTS for r in rates[: 2*len(models)])
 
 
127
  current_start = time.time()
 
 
128
 
129
- header = f"Example {idx+1}/{TOTAL}"
130
- return (
131
- idx, header, row[CONTENT_COL],
132
- *outs, *rates, *comms,
133
- gr.update(visible=True), # back_btn update
134
- gr.update(visible=True, interactive=ready) # next_btn update
135
- )
136
 
137
 
138
  def save_row(idx: int, ratings: List[str], comments: List[str]):
139
- """Persist ratings & comments for example *idx* β†’ CSV."""
140
- if not all(r in RATING_OPTS for r in ratings[: 2*len(models)]):
 
141
  return
142
  elapsed = time.time() - current_start if current_start else 0.0
143
-
144
- order = get_perm(idx)
145
- p = 0 # rating pointer
146
- for m in order:
147
  df.at[idx, f"rating_{m}__prompt1"] = ratings[p]; p += 1
148
  df.at[idx, f"rating_{m}__prompt2"] = ratings[p]; p += 1
149
- # comments
150
- for m, c in zip(order, comments):
151
- clean = (c or "").strip()
152
- df.at[idx, f"{COMMENT_PREFIX}{m}"] = clean or "no comment"
153
-
154
  df.at[idx, "annotator"] = annotator
155
  df.at[idx, "annotation_time"] = float(elapsed)
156
  df.to_csv(csv_path, index=False)
157
 
158
-
159
- def _writable_dir() -> str:
160
- """Return /data on Spaces, /tmp elsewhere – whichever is writeable."""
161
- for d in ("/data", "/tmp"):
162
- try:
163
- os.makedirs(d, exist_ok=True)
164
- with open(os.path.join(d, ".touch"), "w"):
165
- pass
166
- return d
167
- except PermissionError:
168
- continue
169
- raise PermissionError("No writable directory found.")
170
-
171
-
172
- # ─── GRADIO UI ────────────────────────────────────────────────────────────
173
  with gr.Blocks(title="Paragraph Annotation Tool") as demo:
174
- # shared state
175
- idx_state = gr.State(0) # current example index
176
- nmodels_state = gr.State(0) # how many model slots are active
177
 
178
- gr.Markdown("## Paragraph Annotation Tool")
 
 
 
 
179
 
180
- with gr.Row():
181
- upload_box = gr.File(label="Upload / Resume CSV", file_types=[".csv"])
182
- annot_box = gr.Textbox(label="Annotator name")
183
- start_btn = gr.Button("Start / Resume")
184
 
185
- annotator_label = gr.Markdown(visible=False)
 
 
186
 
187
- annotation_area = gr.Column(visible=False)
188
- with annotation_area:
189
  idx_box = gr.Number(label="Index", interactive=False)
190
  hdr_box = gr.Markdown()
191
- para_box = gr.Textbox(lines=6, interactive=False,
192
- label="Content Paragraph")
193
 
194
- # Pre-allocate up to MAX_MODELS slots
195
- out_boxes, radio_widgets, comment_boxes = [], [], []
196
  for _ in range(MAX_MODELS):
197
- with gr.Row():
198
- # prompts + ratings
199
- with gr.Column(scale=2):
200
- out1 = gr.Textbox(lines=6, interactive=False)
201
- rad1 = gr.Radio(RATING_OPTS, label="Rating (P1)", value=None)
202
- with gr.Column(scale=2):
203
- out2 = gr.Textbox(lines=6, interactive=False)
204
- rad2 = gr.Radio(RATING_OPTS, label="Rating (P2)", value=None)
205
- # NEW β†’ comment textbox
206
- with gr.Column(scale=1):
207
- com = gr.Textbox(lines=2, label="Comment", placeholder="Optional…")
208
  out_boxes.extend((out1, out2))
209
  radio_widgets.extend((rad1, rad2))
210
- comment_boxes.append(com)
 
211
 
212
- back_btn = gr.Button("⟡ Back", visible=False)
213
- next_btn = gr.Button("Save & Next ⟢", visible=False)
214
- download_btn = gr.Button("πŸ’Ύ Download CSV", visible=False)
 
215
 
216
- # Enable NEXT when visible radios are filled (comments are optional)
217
- def toggle_next(model_cnt: int, *vals):
218
- needed = vals[: model_cnt*2] # only rating radios
219
- return gr.update(interactive=all(v in RATING_OPTS for v in needed))
220
 
221
  for r in radio_widgets:
222
- r.change(toggle_next,
223
- inputs=[nmodels_state]+radio_widgets,
224
- outputs=next_btn)
225
 
226
- # ── navigation callbacks ──────────────────────────────────────────────
227
  def goto(step: int):
228
- def _fn(idx: int, model_cnt: int, *vals):
229
- """Handle Back / Next logic."""
230
- # structure of *vals*: radios (model_cnt*2) + comments (model_cnt) + next_btn
231
- RADIO_COUNT = MAX_MODELS * 2
232
- ratings = list(vals[: model_cnt * 2])
233
- comments = list(vals[RADIO_COUNT : RADIO_COUNT + model_cnt])
234
-
235
- # save current row unless we attempted to go back without finishing ratings
236
- if step != -1 or all(r in RATING_OPTS for r in ratings):
237
  save_row(idx, ratings, comments)
238
- new_idx = max(0, min(idx+step, TOTAL-1))
239
  return build_row(new_idx)
240
  return _fn
241
 
242
- back_btn.click(
243
- goto(-1),
244
- inputs=[idx_state, nmodels_state]+radio_widgets+comment_boxes+[next_btn],
245
- outputs=[idx_state, hdr_box, para_box,
246
- *out_boxes, *radio_widgets, *comment_boxes,
247
- back_btn, next_btn],
248
- )
249
-
250
- next_btn.click(
251
- goto(1),
252
- inputs=[idx_state, nmodels_state]+radio_widgets+comment_boxes+[next_btn],
253
- outputs=[idx_state, hdr_box, para_box,
254
- *out_boxes, *radio_widgets, *comment_boxes,
255
- back_btn, next_btn],
256
- )
257
-
258
- # CSV download
259
- def make_download():
260
- if df is None:
261
- raise gr.Error("No CSV loaded yet.")
262
- tmp = os.path.join(_writable_dir(),
263
- f"annotations_{uuid.uuid4().hex}.csv")
264
- df.to_csv(tmp, index=False)
265
- return tmp
266
- download_btn.click(make_download, outputs=gr.File())
267
-
268
- # ── Start / Resume ────────────────────────────────────────────────────
269
- def start_app(csv_file, name):
270
- global annotator
271
- if csv_file is None or not name.strip():
272
- raise gr.Error("Please upload a CSV and enter your name.")
273
 
274
- new_path = os.path.join(_writable_dir(), f"{uuid.uuid4().hex}.csv")
275
- shutil.copy(csv_file.name, new_path)
276
- load_csv(new_path)
 
 
277
  annotator = name.strip()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
278
 
279
- # visibility flags – one boolean per model slot
280
- vis_flags = [i < len(models) for i in range(MAX_MODELS)]
281
-
282
- # build first row values
283
- row_vals = build_row(first_incomplete())
284
- idx_val, hdr_val, para_val = row_vals[:3]
285
- outs = row_vals[3 : 3 + MAX_MODELS*2]
286
- rates = row_vals[3 + MAX_MODELS*2 : 3 + MAX_MODELS*4]
287
- comms = row_vals[3 + MAX_MODELS*4 : 3 + MAX_MODELS*5]
288
- back_update, next_update = row_vals[-2:]
289
-
290
- # updates for textboxes, radios, comments
291
- out_updates = [
292
- gr.update(value=outs[i], visible=vis_flags[i//2])
293
- for i in range(MAX_MODELS*2)
294
- ]
295
- radio_updates = [
296
- gr.update(value=rates[i], visible=vis_flags[i//2])
297
- for i in range(MAX_MODELS*2)
298
- ]
299
- comment_updates = [
300
- gr.update(value=comms[i], visible=vis_flags[i])
301
- for i in range(MAX_MODELS)
302
- ]
303
-
304
- return (
305
- first_incomplete(), # idx_state
306
- len(models), # nmodels_state
307
- gr.update(value=idx_val), # idx_box
308
- gr.update(value=hdr_val), # hdr_box
309
- gr.update(value=para_val), # para_box
310
- *out_updates,
311
- *radio_updates,
312
- *comment_updates,
313
- back_update, next_update, # nav buttons
314
- gr.update(visible=True,
315
- value=f"**Annotator:** {annotator}"),
316
- gr.update(visible=True), # download_btn
317
- gr.update(visible=True) # annotation_area
318
- )
319
-
320
- start_btn.click(
321
- start_app,
322
- inputs=[upload_box, annot_box],
323
- outputs=[
324
- idx_state, nmodels_state,
325
- idx_box, hdr_box, para_box,
326
- *out_boxes, *radio_widgets, *comment_boxes,
327
- back_btn, next_btn,
328
- annotator_label,
329
- download_btn,
330
- annotation_area
331
- ],
332
- )
333
-
334
- # ─── RUN ───────────────────────────────────────────────────────────────────
335
  if __name__ == "__main__":
336
  demo.queue()
337
- demo.launch() # keep share=False on HF Spaces
 
 
1
  """
2
+ Paragraph‑level annotation tool for rating two prompts from multipleβ€―LLMs.
3
+
4
+ Patchβ€―3 – show hidden rows
5
+ --------------------------
6
+ * **Bugβ€―fix:** the model rows stayed invisible after you hit **Run**
7
+ because Gradio needs `gr.update(visible=…)` objects returned, not
8
+ on‑the‑fly attribute tweaks. The init callback now returns a
9
+ visibility update for every row container, so you’ll see the prompt,
10
+ rating and comment widgets immediately.
11
+ * Logic still hides surplus rows when your CSV contains fewer than
12
+ `MAX_MODELS` models.
13
+ * No other behaviour changed.
 
 
14
  """
15
  from __future__ import annotations
16
+
17
+ import gradio as gr
18
+ import pandas as pd
19
+ import time, random
20
  from typing import List
21
 
22
+ # ---------- CONFIG ----------
23
+ CONTENT_COL = "Content_Paragraph"
24
+ PROMPT1_SUFFIX = "_prompt1"
25
+ PROMPT2_SUFFIX = "_prompt2"
26
+ PERM_COL = "perm_models"
27
+ RATING_OPTS = ["A", "B", "C"]
28
+ NO_COMMENT = "No comment"
29
+ MAX_MODELS = 8 # UI reserves slots for up to this many models
30
 
31
+ # ---------- GLOBAL STATE ----------
32
  df: pd.DataFrame | None = None
33
+ models: List[str] = []
34
+ csv_path: str = ""
35
+ annotator: str = ""
36
+ TOTAL: int = 0
37
  current_start: float | None = None
38
 
39
+ # ---------- HELPERS ----------
40
+
41
+ def discover_models() -> None:
42
+ global models, df
43
+ models = []
44
+ for c in df.columns:
45
+ if c.endswith(PROMPT1_SUFFIX) and not (
46
+ c.startswith("rating_") or c.startswith("comment_") or
47
+ c in ["perm_models", "annotator", "annotation_time"]
48
+ ):
49
+ m = c[:-len(PROMPT1_SUFFIX)]
 
 
50
  if f"{m}{PROMPT2_SUFFIX}" not in df.columns:
51
+ raise ValueError(f"Found '{c}' but no matching '{m}{PROMPT2_SUFFIX}'")
52
  models.append(m)
 
53
  if not models:
54
+ raise ValueError(f"No '*{PROMPT1_SUFFIX}' columns found in CSV")
55
  if len(models) > MAX_MODELS:
56
+ raise ValueError(f"CSV has {len(models)} model columns but UI can display only {MAX_MODELS}. Increase MAX_MODELS and restart.")
 
57
 
58
+
59
+ def ensure_helper_columns() -> None:
60
+ global df, models
61
  if PERM_COL not in df.columns:
62
  df[PERM_COL] = ""
63
+
64
  for m in models:
 
65
  for p in ("prompt1", "prompt2"):
66
+ rcol = f"rating_{m}__{p}"
67
+ ccol = f"comment_{m}__{p}"
68
+ if rcol not in df.columns:
69
+ df[rcol] = ""
70
+ if ccol not in df.columns:
71
+ df[ccol] = ""
 
72
 
73
  for col in ("annotator", "annotation_time"):
74
  if col not in df.columns:
75
  df[col] = "" if col == "annotator" else 0.0
76
 
77
 
 
 
78
  def first_incomplete() -> int:
79
+ global df, models
80
  for i, row in df.iterrows():
81
  for m in models:
82
+ if row[f"rating_{m}__prompt1"] == "" or row[f"rating_{m}__prompt2"] == "":
 
 
 
83
  return i
84
  return 0
85
 
86
 
87
  def get_perm(idx: int) -> List[str]:
88
+ global df, models
89
+ cell = str(df.at[idx, PERM_COL]).strip()
90
+ if cell:
91
+ seq = cell.split("|")
92
+ if set(seq) == set(models):
93
+ return seq
94
+ seq = models.copy(); random.shuffle(seq)
95
+ df.at[idx, PERM_COL] = "|".join(seq)
96
+ return seq
97
+
98
+ # ---------- ROW I/O ----------
99
 
100
  def build_row(idx: int):
101
+ """Return a list of widget values with length matching *common_outputs*."""
102
+ global df, models, current_start, TOTAL
103
  row = df.loc[idx]
104
  order = get_perm(idx)
105
 
106
+ txt_outputs, ratings, comments = [], [], []
107
  for m in order:
108
+ txt_outputs.extend([
109
+ row[f"{m}{PROMPT1_SUFFIX}"],
110
+ row[f"{m}{PROMPT2_SUFFIX}"],
111
+ ])
112
+ ratings.extend([
113
+ row[f"rating_{m}__prompt1"] or None,
114
+ row[f"rating_{m}__prompt2"] or None,
115
+ ])
116
+ for p in ("prompt1", "prompt2"):
117
+ comments.append(row[f"comment_{m}__{p}"])
118
+
119
+ pad_slots = MAX_MODELS - len(order)
120
+ txt_outputs.extend(["", ""] * pad_slots)
121
+ ratings.extend(["A", "A"] * pad_slots)
122
+ comments.extend(["", ""] * pad_slots)
123
+
124
  current_start = time.time()
125
+ ready = all(r in RATING_OPTS for r in ratings[:2*len(models)])
126
+ header = f"Example {idx + 1}/{TOTAL}"
127
 
128
+ return [idx, idx, header, row[CONTENT_COL]] + \
129
+ txt_outputs + ratings + comments + \
130
+ [gr.update(), gr.update(interactive=ready)]
 
 
 
 
131
 
132
 
133
  def save_row(idx: int, ratings: List[str], comments: List[str]):
134
+ global df, annotator, csv_path, current_start
135
+ needed = 2 * len(models)
136
+ if not all(r in RATING_OPTS for r in ratings[:needed]):
137
  return
138
  elapsed = time.time() - current_start if current_start else 0.0
139
+ p = q = 0
140
+ for m in get_perm(idx):
 
 
141
  df.at[idx, f"rating_{m}__prompt1"] = ratings[p]; p += 1
142
  df.at[idx, f"rating_{m}__prompt2"] = ratings[p]; p += 1
143
+ c1 = comments[q].strip() or NO_COMMENT; q += 1
144
+ c2 = comments[q].strip() or NO_COMMENT; q += 1
145
+ df.at[idx, f"comment_{m}__prompt1"] = c1
146
+ df.at[idx, f"comment_{m}__prompt2"] = c2
 
147
  df.at[idx, "annotator"] = annotator
148
  df.at[idx, "annotation_time"] = float(elapsed)
149
  df.to_csv(csv_path, index=False)
150
 
151
+ # ---------- GRADIO ----------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  with gr.Blocks(title="Paragraph Annotation Tool") as demo:
153
+ gr.Markdown("# Paragraph Annotation Tool")
 
 
154
 
155
+ # Setup panel
156
+ with gr.Column() as setup_panel:
157
+ csv_upload = gr.File(label="Upload CSV", file_types=[".csv"])
158
+ name_input = gr.Textbox(label="Your Name")
159
+ run_btn = gr.Button("Run")
160
 
161
+ annotator_md = gr.Markdown(visible=False)
 
 
 
162
 
163
+ # Annotation panel (hidden until CSV is loaded)
164
+ with gr.Column(visible=False) as annotation_panel:
165
+ state = gr.State(0)
166
 
 
 
167
  idx_box = gr.Number(label="Index", interactive=False)
168
  hdr_box = gr.Markdown()
169
+ para_box = gr.Textbox(label="Content Paragraph", interactive=False, lines=6)
 
170
 
171
+ out_boxes, radio_widgets, comment_widgets = [], [], []
172
+ row_containers = []
173
  for _ in range(MAX_MODELS):
174
+ with gr.Row(visible=False) as r:
175
+ with gr.Column():
176
+ out1 = gr.Textbox(interactive=False, lines=6)
177
+ rad1 = gr.Radio(RATING_OPTS, label="Rating (P1)")
178
+ com1 = gr.Textbox(lines=2, label="Comment (P1)")
179
+ with gr.Column():
180
+ out2 = gr.Textbox(interactive=False, lines=6)
181
+ rad2 = gr.Radio(RATING_OPTS, label="Rating (P2)")
182
+ com2 = gr.Textbox(lines=2, label="Comment (P2)")
 
 
183
  out_boxes.extend((out1, out2))
184
  radio_widgets.extend((rad1, rad2))
185
+ comment_widgets.extend((com1, com2))
186
+ row_containers.append(r)
187
 
188
+ back_btn = gr.Button("⟡ Back")
189
+ next_btn = gr.Button("Save & Next ⟢", interactive=False)
190
+ download_btn = gr.Button("Download CSV")
191
+ csv_file_out = gr.File()
192
 
193
+ # ---------- CALLBACKS ----------
194
+ def toggle_next(*vals):
195
+ needed = 2 * len(models)
196
+ return gr.update(interactive=all(v in RATING_OPTS for v in vals[:needed]))
197
 
198
  for r in radio_widgets:
199
+ r.change(toggle_next, inputs=radio_widgets, outputs=next_btn)
 
 
200
 
 
201
  def goto(step: int):
202
+ def _fn(idx: int, *vals):
203
+ n_rad = len(radio_widgets)
204
+ ratings = list(vals[:n_rad])
205
+ comments = list(vals[n_rad:-1])
206
+ if step != -1 or all(r in RATING_OPTS for r in ratings[:2*len(models)]):
 
 
 
 
207
  save_row(idx, ratings, comments)
208
+ new_idx = max(0, min(idx + step, TOTAL - 1))
209
  return build_row(new_idx)
210
  return _fn
211
 
212
+ common_inputs = [state] + radio_widgets + comment_widgets + [next_btn]
213
+ common_outputs = [state, idx_box, hdr_box, para_box] + \
214
+ out_boxes + radio_widgets + comment_widgets + \
215
+ [back_btn, next_btn]
216
+
217
+ back_btn.click(goto(-1), inputs=common_inputs, outputs=common_outputs)
218
+ next_btn.click(goto(1), inputs=common_inputs, outputs=common_outputs)
219
+
220
+ download_btn.click(lambda: csv_path, outputs=csv_file_out)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
 
222
+ # ---------- INIT ----------
223
+ def init_annotation(uploaded_file, name):
224
+ global df, csv_path, annotator, TOTAL
225
+ if uploaded_file is None or not name.strip():
226
+ raise gr.Error("Please upload a CSV and enter your name.")
227
  annotator = name.strip()
228
+ csv_path = uploaded_file.name
229
+ local_df = pd.read_csv(csv_path, keep_default_na=False)
230
+ if CONTENT_COL not in local_df.columns:
231
+ raise gr.Error(f"Missing required column '{CONTENT_COL}' in CSV")
232
+ globals()['df'] = local_df
233
+ discover_models()
234
+ ensure_helper_columns()
235
+ TOTAL = len(df)
236
+ df.to_csv(csv_path, index=False)
237
+
238
+ first_idx = first_incomplete()
239
+ row_vals = build_row(first_idx)
240
+
241
+ # visibility updates for rows
242
+ vis_updates = [gr.update(visible=i < len(models)) for i in range(MAX_MODELS)]
243
+
244
+ return [f"**Annotator:** {annotator}", gr.update(visible=True)] + vis_updates + row_vals
245
+
246
+ # run_outputs: annotator_md, annotation_panel, row_vis_updates..., common_outputs
247
+ run_outputs = [annotator_md, annotation_panel] + [gr.Row()]*MAX_MODELS + common_outputs
248
+ # substitute placeholder Rows with actual containers
249
+ run_outputs[2:2+MAX_MODELS] = row_containers
250
+
251
+ run_btn.click(init_annotation, inputs=[csv_upload, name_input], outputs=run_outputs)
252
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
253
  if __name__ == "__main__":
254
  demo.queue()
255
+ demo.launch(share=True)