ouhenio commited on
Commit
f8842a2
·
verified ·
1 Parent(s): 8eec983

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +290 -295
app.py CHANGED
@@ -58,11 +58,10 @@ for country_code, data in COUNTRY_MAPPING.items():
58
 
59
  # Set up the webhook listener for response creation
60
  @webhook_listener(events=["response.created"])
61
- async def update_validation_space_on_answer(response, type, timestamp):
62
  """
63
- Webhook listener that triggers when a new response is added to an answering space.
64
- It will automatically update the corresponding validation space with the new response
65
- and update the country's annotation progress.
66
  """
67
  try:
68
  # Store the event for display in the UI
@@ -71,55 +70,19 @@ async def update_validation_space_on_answer(response, type, timestamp):
71
  # Get the record from the response
72
  record = response.record
73
 
74
- # Check if this is from an answering space
75
  dataset_name = record.dataset.name
76
- if not dataset_name.endswith("_responder_preguntas"):
77
- print(f"Ignoring event from non-answering dataset: {dataset_name}")
78
- return # Not an answering space, ignore
79
-
80
- # Extract the country from the dataset name
81
- country = dataset_name.replace("_responder_preguntas", "")
82
- print(f"Processing response for country: {country}")
83
-
84
- # Connect to the validation space
85
- validation_dataset_name = f"{country}_validar_respuestas"
86
- try:
87
- validation_dataset = client.datasets(validation_dataset_name)
88
- print(f"Found validation dataset: {validation_dataset_name}")
89
- except Exception as e:
90
- print(f"Error connecting to validation dataset: {e}")
91
- # You would need to import the create_validation_space function
92
- from build_space import create_validation_space
93
- validation_dataset = create_validation_space(country)
94
-
95
- response_dict = response.to_dict()
96
- answer = response_dict["values"]['text']['value']
97
-
98
- # Get the user ID of the original responder
99
- original_user_id = str(response.user_id)
100
-
101
- # Create a validation record with the correct attribute
102
- validation_record = {
103
- "question": record.fields["question"],
104
- "answer": answer,
105
- "metadata": {
106
- "original_responder_id": original_user_id,
107
- "original_dataset": dataset_name
108
- }
109
- }
110
-
111
- # Add the record to the validation space
112
- validation_dataset.records.log(records=[validation_record])
113
- print(f"Added new response to validation space for {country}")
114
-
115
- # Update the annotation progress
116
- # Get the country code from the country name or substring
117
  country_code = None
118
  for code, data in COUNTRY_MAPPING.items():
119
- if data["name"].lower() in country.lower():
 
120
  country_code = code
121
  break
122
-
 
123
  if country_code and country_code in annotation_progress:
124
  # Increment the count
125
  annotation_progress[country_code]["count"] += 1
@@ -137,283 +100,310 @@ async def update_validation_space_on_answer(response, type, timestamp):
137
  "count": count,
138
  "percent": percent
139
  })
 
140
 
141
  except Exception as e:
142
  print(f"Error in webhook handler: {e}")
143
  # Store the error in the queue for display
144
  incoming_events.put({"event": "error", "error": str(e)})
145
 
146
- # Function to read the next event from the queue and update UI elements
147
  def read_next_event():
148
  if not incoming_events.empty():
149
  event = incoming_events.get()
150
  return event
151
  return {}
152
 
153
- # Function to get the current annotation progress data
154
- def get_annotation_progress():
155
- return json.dumps(annotation_progress)
 
 
 
 
 
156
 
157
- # D3.js map visualization HTML template
158
- def create_map_html(progress_data):
 
 
 
 
 
 
 
 
 
 
 
159
  return f"""
160
- <!DOCTYPE html>
161
- <html>
162
- <head>
163
- <script src="https://d3js.org/d3.v7.min.js"></script>
164
- <script src="https://d3js.org/d3-geo.v2.min.js"></script>
165
- <script src="https://d3js.org/topojson.v3.min.js"></script>
166
- <style>
167
- .country {{
168
- stroke: #f32b7b;
169
- stroke-width: 1px;
170
- }}
171
- .country:hover {{
172
- stroke: #4a1942;
173
- stroke-width: 2px;
174
- cursor: pointer;
175
- }}
176
- .tooltip {{
177
- position: absolute;
178
- background-color: rgba(0, 0, 0, 0.8);
179
- border-radius: 5px;
180
- padding: 8px;
181
- color: white;
182
- font-size: 12px;
183
- pointer-events: none;
184
- opacity: 0;
185
- transition: opacity 0.3s;
186
- }}
187
- text {{
188
- fill: #f1f5f9;
189
- font-family: sans-serif;
190
- }}
191
- .legend-text {{
192
- fill: #94a3b8;
193
- font-size: 10px;
194
- }}
195
- </style>
196
- </head>
197
- <body style="margin:0; background-color:#111;">
198
- <div id="map-container" style="width:100%; height:600px; position:relative;"></div>
199
- <div id="tooltip" class="tooltip"></div>
200
-
201
- <script>
202
- // The progress data passed from Python
203
- const progressData = {progress_data};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
204
 
205
- // Set up the SVG container
206
- const width = document.getElementById('map-container').clientWidth;
207
- const height = 600;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
 
209
- const svg = d3.select("#map-container")
210
- .append("svg")
211
  .attr("width", width)
212
- .attr("height", height)
213
- .attr("viewBox", `0 0 ${{width}} ${{height}}`)
214
- .style("background-color", "#111");
215
-
216
- // Define color scale
217
- const colorScale = d3.scaleLinear()
218
- .domain([0, 100])
219
- .range(["#4a1942", "#f32b7b"]);
220
 
221
- // Set up projection focused on Latin America and Spain
222
- const projection = d3.geoMercator()
223
- .center([-60, 0])
224
- .scale(width / 5)
225
  .translate([width / 2, height / 2]);
226
-
227
- const path = d3.geoPath().projection(projection);
228
 
229
- // Tooltip setup
230
- const tooltip = d3.select("#tooltip");
231
 
232
- // Load the world GeoJSON data
233
- d3.json("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson")
234
- .then(data => {{
235
- // Filter for Latin American countries and Spain
236
- const relevantCountryCodes = Object.keys(progressData);
237
-
238
- // Draw the map
239
- svg.selectAll("path")
240
- .data(data.features)
241
- .enter()
242
- .append("path")
243
- .attr("d", path)
244
- .attr("class", "country")
245
- .attr("fill", d => {{
246
- // Get the ISO code from the properties
247
- const iso = d.properties.iso_a2;
248
-
249
- if (progressData[iso]) {{
250
- return colorScale(progressData[iso].percent);
251
- }}
252
- return "#2d3748"; // Default gray for non-tracked countries
253
- }})
254
- .on("mouseover", function(event, d) {{
255
- const iso = d.properties.iso_a2;
256
-
257
- if (progressData[iso]) {{
258
- tooltip.style("opacity", 1)
259
- .html(`
260
- <strong>${{progressData[iso].name}}</strong><br/>
261
- Documents: ${{progressData[iso].count.toLocaleString()}}/${{progressData[iso].target.toLocaleString()}}<br/>
262
- Completion: ${{progressData[iso].percent}}%
263
- `);
264
- }}
265
- }})
266
- .on("mousemove", function(event) {{
267
- tooltip.style("left", (event.pageX + 15) + "px")
268
- .style("top", (event.pageY + 15) + "px");
269
- }})
270
- .on("mouseout", function() {{
271
- tooltip.style("opacity", 0);
272
- }});
273
-
274
- // Add legend
275
- const legendWidth = Math.min(width - 40, 200);
276
- const legendHeight = 15;
277
- const legendX = width - legendWidth - 20;
278
-
279
- const legend = svg.append("g")
280
- .attr("class", "legend")
281
- .attr("transform", `translate(${{legendX}}, 30)`);
282
-
283
- // Create gradient for legend
284
- const defs = svg.append("defs");
285
- const gradient = defs.append("linearGradient")
286
- .attr("id", "dataGradient")
287
- .attr("x1", "0%")
288
- .attr("y1", "0%")
289
- .attr("x2", "100%")
290
- .attr("y2", "0%");
291
-
292
- gradient.append("stop")
293
- .attr("offset", "0%")
294
- .attr("stop-color", "#4a1942");
295
-
296
- gradient.append("stop")
297
- .attr("offset", "100%")
298
- .attr("stop-color", "#f32b7b");
299
-
300
- // Add legend title
301
- legend.append("text")
302
- .attr("x", legendWidth / 2)
303
- .attr("y", -10)
304
- .attr("text-anchor", "middle")
305
- .attr("font-size", "12px")
306
- .text("Annotation Progress");
307
-
308
- // Add legend rectangle
309
- legend.append("rect")
310
- .attr("width", legendWidth)
311
- .attr("height", legendHeight)
312
- .attr("rx", 2)
313
- .attr("ry", 2)
314
- .style("fill", "url(#dataGradient)");
315
-
316
- // Add legend labels
317
- legend.append("text")
318
- .attr("class", "legend-text")
319
- .attr("x", 0)
320
- .attr("y", legendHeight + 15)
321
- .attr("text-anchor", "start")
322
- .text("0%");
323
-
324
- legend.append("text")
325
- .attr("class", "legend-text")
326
- .attr("x", legendWidth / 2)
327
- .attr("y", legendHeight + 15)
328
- .attr("text-anchor", "middle")
329
- .text("50%");
330
-
331
- legend.append("text")
332
- .attr("class", "legend-text")
333
- .attr("x", legendWidth)
334
- .attr("y", legendHeight + 15)
335
- .attr("text-anchor", "end")
336
- .text("100%");
337
- }});
338
-
339
- // Handle window resize
340
- window.addEventListener('resize', () => {{
341
- const width = document.getElementById('map-container').clientWidth;
342
-
343
- // Update SVG dimensions
344
- d3.select("svg")
345
- .attr("width", width)
346
- .attr("viewBox", `0 0 ${{width}} ${{height}}`);
347
-
348
- // Update projection
349
- projection.scale(width / 5)
350
- .translate([width / 2, height / 2]);
351
-
352
- // Update paths
353
- d3.selectAll("path").attr("d", path);
354
-
355
- // Update legend position
356
- const legendWidth = Math.min(width - 40, 200);
357
- const legendX = width - legendWidth - 20;
358
-
359
- d3.select(".legend")
360
- .attr("transform", `translate(${{legendX}}, 30)`);
361
- }});
362
- </script>
363
- </body>
364
- </html>
365
  """
366
 
 
 
 
 
 
367
  # Create Gradio interface
368
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="pink", secondary_hue="purple")) as demo:
369
  argilla_server = client.http_client.base_url if hasattr(client, 'http_client') else "Not connected"
370
 
371
  with gr.Row():
372
  gr.Markdown(f"""
373
- # Argilla Annotation Progress Map
374
 
375
  ### Connected to Argilla server: {argilla_server}
376
 
377
  This dashboard visualizes annotation progress across Latin America and Spain.
378
- The webhook listens for `response.created` events from datasets ending with `_responder_preguntas`.
379
  """)
380
 
381
  with gr.Row():
382
  with gr.Column(scale=2):
383
- # Map visualization
384
- map_html = gr.HTML(create_map_html(json.dumps(annotation_progress)), label="Annotation Progress Map")
385
 
386
- # Stats section
387
- with gr.Accordion("Overall Statistics", open=False):
388
- total_docs = gr.Number(value=0, label="Total Documents Collected")
389
- avg_completion = gr.Number(value=0, label="Average Completion (%)")
390
- countries_over_50 = gr.Number(value=0, label="Countries Over 50% Complete")
391
 
392
  with gr.Column(scale=1):
393
  # Recent events log
394
  events_json = gr.JSON(label="Recent Events", value={})
395
 
 
 
 
 
 
396
  # Country details
397
- with gr.Accordion("Country Details", open=True):
398
- country_selector = gr.Dropdown(
399
- choices=[f"{data['name']} ({code})" for code, data in COUNTRY_MAPPING.items()],
400
- label="Select Country"
401
- )
402
- country_progress = gr.JSON(label="Country Progress", value={})
403
 
404
- # Functions to update the UI
405
- def update_map():
406
- progress_json = json.dumps(annotation_progress)
407
- return create_map_html(progress_json)
408
-
409
- def update_stats():
410
- total = sum(data["count"] for data in annotation_progress.values())
411
- percentages = [data["percent"] for data in annotation_progress.values()]
412
- avg = sum(percentages) / len(percentages) if percentages else 0
413
- countries_50_plus = sum(1 for p in percentages if p >= 50)
414
 
415
- return total, avg, countries_50_plus
 
 
 
 
 
 
 
 
 
 
 
 
 
 
416
 
 
417
  def update_country_details(country_selection):
418
  if not country_selection:
419
  return {}
@@ -424,30 +414,35 @@ with gr.Blocks(theme=gr.themes.Soft(primary_hue="pink", secondary_hue="purple"))
424
  if code in annotation_progress:
425
  return annotation_progress[code]
426
  return {}
427
-
428
- # Simple event processing functions for better compatibility
429
- def update_events():
430
- return read_next_event()
431
-
432
- def update_all_stats():
433
- return update_stats()
434
-
435
- # Use separate timers for each component for better compatibility
436
- gr.Timer(1, active=True).tick(update_events, outputs=events_json)
437
- gr.Timer(5, active=True).tick(update_all_stats, outputs=[total_docs, avg_completion, countries_over_50])
438
 
 
439
  country_selector.change(
440
  fn=update_country_details,
441
  inputs=[country_selector],
442
  outputs=[country_progress]
443
  )
444
 
445
- # Use refresh button instead of timer for map updates (more compatible across versions)
446
- refresh_btn = gr.Button("Refresh Map")
447
- refresh_btn.click(
448
- fn=update_map,
449
- inputs=None,
450
- outputs=[map_html]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
451
  )
452
 
453
  # Mount the Gradio app to the FastAPI server
 
58
 
59
  # Set up the webhook listener for response creation
60
  @webhook_listener(events=["response.created"])
61
+ async def update_annotation_progress(response, type, timestamp):
62
  """
63
+ Webhook listener that triggers when a new response is added to an Argilla dataset.
64
+ It will update the annotation progress for the corresponding country.
 
65
  """
66
  try:
67
  # Store the event for display in the UI
 
70
  # Get the record from the response
71
  record = response.record
72
 
73
+ # Get dataset name
74
  dataset_name = record.dataset.name
75
+ print(f"Processing response for dataset: {dataset_name}")
76
+
77
+ # Try to determine the country from the dataset name
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  country_code = None
79
  for code, data in COUNTRY_MAPPING.items():
80
+ country_name = data["name"].lower()
81
+ if country_name in dataset_name.lower():
82
  country_code = code
83
  break
84
+
85
+ # If we found a matching country, update its progress
86
  if country_code and country_code in annotation_progress:
87
  # Increment the count
88
  annotation_progress[country_code]["count"] += 1
 
100
  "count": count,
101
  "percent": percent
102
  })
103
+ print(f"Updated progress for {annotation_progress[country_code]['name']}: {percent}%")
104
 
105
  except Exception as e:
106
  print(f"Error in webhook handler: {e}")
107
  # Store the error in the queue for display
108
  incoming_events.put({"event": "error", "error": str(e)})
109
 
110
+ # Function to read the next event from the queue
111
  def read_next_event():
112
  if not incoming_events.empty():
113
  event = incoming_events.get()
114
  return event
115
  return {}
116
 
117
+ # Function to calculate overall statistics
118
+ def update_stats():
119
+ total = sum(data["count"] for data in annotation_progress.values())
120
+ percentages = [data["percent"] for data in annotation_progress.values()]
121
+ avg = sum(percentages) / len(percentages) if percentages else 0
122
+ countries_50_plus = sum(1 for p in percentages if p >= 50)
123
+
124
+ return total, avg, countries_50_plus
125
 
126
+ # Create the map HTML container (without D3.js code)
127
+ def create_map_html():
128
+ return """
129
+ <div id="map-container" style="width:100%; height:600px; position:relative; background-color:#111;">
130
+ <div style="display:flex; justify-content:center; align-items:center; height:100%; color:white; font-family:sans-serif;">
131
+ Loading map visualization...
132
+ </div>
133
+ </div>
134
+ <div id="tooltip" style="position:absolute; background-color:rgba(0,0,0,0.8); border-radius:5px; padding:8px; color:white; font-size:12px; pointer-events:none; opacity:0; transition:opacity 0.3s;"></div>
135
+ """
136
+
137
+ # Create D3.js script that will be loaded via Gradio's JavaScript execution
138
+ def create_d3_script(progress_data):
139
  return f"""
140
+ async () => {{
141
+ // Load D3.js modules
142
+ const script1 = document.createElement("script");
143
+ script1.src = "https://cdn.jsdelivr.net/npm/d3@7";
144
+ document.head.appendChild(script1);
145
+
146
+ // Wait for D3 to load
147
+ await new Promise(resolve => {{
148
+ script1.onload = resolve;
149
+ }});
150
+
151
+ console.log("D3 loaded successfully");
152
+
153
+ // Load topojson
154
+ const script2 = document.createElement("script");
155
+ script2.src = "https://cdn.jsdelivr.net/npm/topojson@3";
156
+ document.head.appendChild(script2);
157
+
158
+ await new Promise(resolve => {{
159
+ script2.onload = resolve;
160
+ }});
161
+
162
+ console.log("TopoJSON loaded successfully");
163
+
164
+ // The progress data passed from Python
165
+ const progressData = {progress_data};
166
+
167
+ // Set up the SVG container
168
+ const mapContainer = document.getElementById('map-container');
169
+ mapContainer.innerHTML = ''; // Clear loading message
170
+
171
+ const width = mapContainer.clientWidth;
172
+ const height = 600;
173
+
174
+ const svg = d3.select("#map-container")
175
+ .append("svg")
176
+ .attr("width", width)
177
+ .attr("height", height)
178
+ .attr("viewBox", `0 0 ${{width}} ${{height}}`)
179
+ .style("background-color", "#111");
180
+
181
+ // Define color scale
182
+ const colorScale = d3.scaleLinear()
183
+ .domain([0, 100])
184
+ .range(["#4a1942", "#f32b7b"]);
185
+
186
+ // Set up projection focused on Latin America and Spain
187
+ const projection = d3.geoMercator()
188
+ .center([-60, 0])
189
+ .scale(width / 5)
190
+ .translate([width / 2, height / 2]);
191
+
192
+ const path = d3.geoPath().projection(projection);
193
+
194
+ // Tooltip setup
195
+ const tooltip = d3.select("#tooltip");
196
+
197
+ // Load the world GeoJSON data
198
+ const response = await fetch("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson");
199
+ const data = await response.json();
200
+
201
+ // Draw the map
202
+ svg.selectAll("path")
203
+ .data(data.features)
204
+ .enter()
205
+ .append("path")
206
+ .attr("d", path)
207
+ .attr("stroke", "#f32b7b")
208
+ .attr("stroke-width", 1)
209
+ .attr("fill", d => {{
210
+ // Get the ISO code from the properties
211
+ const iso = d.properties.iso_a2;
212
+
213
+ if (progressData[iso]) {{
214
+ return colorScale(progressData[iso].percent);
215
+ }}
216
+ return "#2d3748"; // Default gray for non-tracked countries
217
+ }})
218
+ .on("mouseover", function(event, d) {{
219
+ const iso = d.properties.iso_a2;
220
+
221
+ d3.select(this)
222
+ .attr("stroke", "#4a1942")
223
+ .attr("stroke-width", 2);
224
+
225
+ if (progressData[iso]) {{
226
+ tooltip.style("opacity", 1)
227
+ .style("left", (event.pageX + 15) + "px")
228
+ .style("top", (event.pageY + 15) + "px")
229
+ .html(`
230
+ <strong>${{progressData[iso].name}}</strong><br/>
231
+ Documents: ${{progressData[iso].count.toLocaleString()}}/${{progressData[iso].target.toLocaleString()}}<br/>
232
+ Completion: ${{progressData[iso].percent}}%
233
+ `);
234
+ }}
235
+ }})
236
+ .on("mousemove", function(event) {{
237
+ tooltip.style("left", (event.pageX + 15) + "px")
238
+ .style("top", (event.pageY + 15) + "px");
239
+ }})
240
+ .on("mouseout", function() {{
241
+ d3.select(this)
242
+ .attr("stroke", "#f32b7b")
243
+ .attr("stroke-width", 1);
244
+
245
+ tooltip.style("opacity", 0);
246
+ }});
247
 
248
+ // Add legend
249
+ const legendWidth = Math.min(width - 40, 200);
250
+ const legendHeight = 15;
251
+ const legendX = width - legendWidth - 20;
252
+
253
+ const legend = svg.append("g")
254
+ .attr("class", "legend")
255
+ .attr("transform", `translate(${{legendX}}, 30)`);
256
+
257
+ // Create gradient for legend
258
+ const defs = svg.append("defs");
259
+ const gradient = defs.append("linearGradient")
260
+ .attr("id", "dataGradient")
261
+ .attr("x1", "0%")
262
+ .attr("y1", "0%")
263
+ .attr("x2", "100%")
264
+ .attr("y2", "0%");
265
+
266
+ gradient.append("stop")
267
+ .attr("offset", "0%")
268
+ .attr("stop-color", "#4a1942");
269
+
270
+ gradient.append("stop")
271
+ .attr("offset", "100%")
272
+ .attr("stop-color", "#f32b7b");
273
+
274
+ // Add legend title
275
+ legend.append("text")
276
+ .attr("x", legendWidth / 2)
277
+ .attr("y", -10)
278
+ .attr("text-anchor", "middle")
279
+ .attr("font-size", "12px")
280
+ .attr("fill", "#f1f5f9")
281
+ .text("Annotation Progress");
282
+
283
+ // Add legend rectangle
284
+ legend.append("rect")
285
+ .attr("width", legendWidth)
286
+ .attr("height", legendHeight)
287
+ .attr("rx", 2)
288
+ .attr("ry", 2)
289
+ .style("fill", "url(#dataGradient)");
290
+
291
+ // Add legend labels
292
+ legend.append("text")
293
+ .attr("x", 0)
294
+ .attr("y", legendHeight + 15)
295
+ .attr("text-anchor", "start")
296
+ .attr("font-size", "10px")
297
+ .attr("fill", "#94a3b8")
298
+ .text("0%");
299
+
300
+ legend.append("text")
301
+ .attr("x", legendWidth / 2)
302
+ .attr("y", legendHeight + 15)
303
+ .attr("text-anchor", "middle")
304
+ .attr("font-size", "10px")
305
+ .attr("fill", "#94a3b8")
306
+ .text("50%");
307
+
308
+ legend.append("text")
309
+ .attr("x", legendWidth)
310
+ .attr("y", legendHeight + 15)
311
+ .attr("text-anchor", "end")
312
+ .attr("font-size", "10px")
313
+ .attr("fill", "#94a3b8")
314
+ .text("100%");
315
+
316
+ // Handle window resize
317
+ globalThis.resizeMap = () => {{
318
+ const width = mapContainer.clientWidth;
319
 
320
+ // Update SVG dimensions
321
+ d3.select("svg")
322
  .attr("width", width)
323
+ .attr("viewBox", `0 0 ${{width}} ${{height}}`);
 
 
 
 
 
 
 
324
 
325
+ // Update projection
326
+ projection.scale(width / 5)
 
 
327
  .translate([width / 2, height / 2]);
 
 
328
 
329
+ // Update paths
330
+ d3.selectAll("path").attr("d", path);
331
 
332
+ // Update legend position
333
+ const legendWidth = Math.min(width - 40, 200);
334
+ const legendX = width - legendWidth - 20;
335
+
336
+ d3.select(".legend")
337
+ .attr("transform", `translate(${{legendX}}, 30)`);
338
+ }};
339
+
340
+ window.addEventListener('resize', globalThis.resizeMap);
341
+ }}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
342
  """
343
 
344
+ # Function to update the map data and trigger a reload
345
+ def update_map():
346
+ progress_json = json.dumps(annotation_progress)
347
+ return progress_json
348
+
349
  # Create Gradio interface
350
  with gr.Blocks(theme=gr.themes.Soft(primary_hue="pink", secondary_hue="purple")) as demo:
351
  argilla_server = client.http_client.base_url if hasattr(client, 'http_client') else "Not connected"
352
 
353
  with gr.Row():
354
  gr.Markdown(f"""
355
+ # Latin America & Spain Annotation Progress Map
356
 
357
  ### Connected to Argilla server: {argilla_server}
358
 
359
  This dashboard visualizes annotation progress across Latin America and Spain.
 
360
  """)
361
 
362
  with gr.Row():
363
  with gr.Column(scale=2):
364
+ # Map visualization - empty at first
365
+ map_html = gr.HTML(create_map_html(), label="Annotation Progress Map")
366
 
367
+ # Hidden element to store map data
368
+ map_data = gr.JSON(value=json.dumps(annotation_progress), visible=False)
 
 
 
369
 
370
  with gr.Column(scale=1):
371
  # Recent events log
372
  events_json = gr.JSON(label="Recent Events", value={})
373
 
374
+ # Overall statistics
375
+ total_docs = gr.Number(value=0, label="Total Documents", interactive=False)
376
+ avg_completion = gr.Number(value=0, label="Average Completion (%)", interactive=False)
377
+ countries_over_50 = gr.Number(value=0, label="Countries Over 50%", interactive=False)
378
+
379
  # Country details
380
+ country_selector = gr.Dropdown(
381
+ choices=[f"{data['name']} ({code})" for code, data in COUNTRY_MAPPING.items()],
382
+ label="Select Country"
383
+ )
384
+ country_progress = gr.JSON(label="Country Progress", value={})
 
385
 
386
+ # Load the D3 script when data is updated
387
+ def load_map_script(data):
388
+ return None, create_d3_script(data)
 
 
 
 
 
 
 
389
 
390
+ # Refresh button
391
+ refresh_btn = gr.Button("Refresh Map")
392
+ refresh_btn.click(
393
+ fn=update_map,
394
+ inputs=None,
395
+ outputs=map_data
396
+ )
397
+
398
+ # When map_data is updated, reload the D3 script
399
+ map_data.change(
400
+ fn=load_map_script,
401
+ inputs=map_data,
402
+ outputs=[None, None],
403
+ _js=create_d3_script(json.dumps(annotation_progress))
404
+ )
405
 
406
+ # Function to update country details
407
  def update_country_details(country_selection):
408
  if not country_selection:
409
  return {}
 
414
  if code in annotation_progress:
415
  return annotation_progress[code]
416
  return {}
 
 
 
 
 
 
 
 
 
 
 
417
 
418
+ # Update country details when a country is selected
419
  country_selector.change(
420
  fn=update_country_details,
421
  inputs=[country_selector],
422
  outputs=[country_progress]
423
  )
424
 
425
+ # Update events function for the timer
426
+ def update_events():
427
+ event = read_next_event()
428
+
429
+ # Calculate stats
430
+ stats = update_stats()
431
+
432
+ # If this is a progress update, update the map data
433
+ if event.get("event") == "progress_update":
434
+ # This will indirectly trigger a map refresh through the change event
435
+ return event, json.dumps(annotation_progress), stats[0], stats[1], stats[2]
436
+
437
+ return event, None, stats[0], stats[1], stats[2]
438
+
439
+ # Make final updates to Gradio demo
440
+ demo.load(None, None, None, _js=create_d3_script(json.dumps(annotation_progress)))
441
+
442
+ # Use timer to check for new events and update stats
443
+ gr.Timer(1, active=True).tick(
444
+ update_events,
445
+ outputs=[events_json, map_data, total_docs, avg_completion, countries_over_50]
446
  )
447
 
448
  # Mount the Gradio app to the FastAPI server