ouhenio commited on
Commit
d7ed39d
·
verified ·
1 Parent(s): 06378db

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +374 -16
app.py CHANGED
@@ -1,5 +1,6 @@
1
  import os
2
  from queue import Queue
 
3
  import gradio as gr
4
  import argilla as rg
5
  from argilla.webhooks import webhook_listener
@@ -16,12 +17,52 @@ server = rg.get_webhook_server()
16
  # Queue to store events for display
17
  incoming_events = Queue()
18
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  # Set up the webhook listener for response creation
20
  @webhook_listener(events=["response.created"])
21
  async def update_validation_space_on_answer(response, type, timestamp):
22
  """
23
  Webhook listener that triggers when a new response is added to an answering space.
24
- It will automatically update the corresponding validation space with the new response.
 
25
  """
26
  try:
27
  # Store the event for display in the UI
@@ -58,8 +99,6 @@ async def update_validation_space_on_answer(response, type, timestamp):
58
  original_user_id = str(response.user_id)
59
 
60
  # Create a validation record with the correct attribute
61
- # Instead of response.value, we need to find the correct attribute
62
- # Based on the error, let's try to get the value differently
63
  validation_record = {
64
  "question": record.fields["question"],
65
  "answer": answer,
@@ -73,6 +112,32 @@ async def update_validation_space_on_answer(response, type, timestamp):
73
  validation_dataset.records.log(records=[validation_record])
74
  print(f"Added new response to validation space for {country}")
75
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
  except Exception as e:
77
  print(f"Error in webhook handler: {e}")
78
  # Store the error in the queue for display
@@ -84,23 +149,303 @@ def read_next_event():
84
  return incoming_events.get()
85
  return {}
86
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  # Create Gradio interface
88
- with gr.Blocks() as demo:
89
- argilla_server = client.http_client.base_url
90
- gr.Markdown("## Argilla Webhooks - Validation Space Updater")
91
- gr.Markdown(f"""
92
- This application listens for new responses in Argilla answering spaces and automatically
93
- adds them to the corresponding validation spaces.
 
 
94
 
95
- Connected to Argilla server: {argilla_server}
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
- The webhook listens for:
98
- - `response.created` events from datasets ending with `_responder_preguntas`
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
 
100
- You can view the incoming events and any errors in the JSON component below.
101
- """)
102
- json_component = gr.JSON(label="Incoming events and errors:", value={})
103
- gr.Timer(1, active=True).tick(read_next_event, outputs=json_component)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
104
 
105
  # Mount the Gradio app to the FastAPI server
106
  gr.mount_gradio_app(server, demo, path="/")
@@ -108,4 +453,17 @@ gr.mount_gradio_app(server, demo, path="/")
108
  # Start the FastAPI server
109
  if __name__ == "__main__":
110
  import uvicorn
 
 
 
 
 
 
 
 
 
 
 
 
 
111
  uvicorn.run(server, host="0.0.0.0", port=7860)
 
1
  import os
2
  from queue import Queue
3
+ import json
4
  import gradio as gr
5
  import argilla as rg
6
  from argilla.webhooks import webhook_listener
 
17
  # Queue to store events for display
18
  incoming_events = Queue()
19
 
20
+ # Dictionary to store annotation progress by country
21
+ annotation_progress = {
22
+ # Format will be:
23
+ # "country_code": {"count": 0, "percent": 0, "name": "Country Name"}
24
+ }
25
+
26
+ # Country mapping (ISO code to name)
27
+ COUNTRY_MAPPING = {
28
+ "MX": {"name": "Mexico", "target": 1000},
29
+ "AR": {"name": "Argentina", "target": 800},
30
+ "CO": {"name": "Colombia", "target": 700},
31
+ "CL": {"name": "Chile", "target": 600},
32
+ "PE": {"name": "Peru", "target": 600},
33
+ "ES": {"name": "Spain", "target": 1200},
34
+ "BR": {"name": "Brazil", "target": 1000},
35
+ "VE": {"name": "Venezuela", "target": 500},
36
+ "EC": {"name": "Ecuador", "target": 400},
37
+ "BO": {"name": "Bolivia", "target": 300},
38
+ "PY": {"name": "Paraguay", "target": 300},
39
+ "UY": {"name": "Uruguay", "target": 300},
40
+ "CR": {"name": "Costa Rica", "target": 250},
41
+ "PA": {"name": "Panama", "target": 250},
42
+ "DO": {"name": "Dominican Republic", "target": 300},
43
+ "GT": {"name": "Guatemala", "target": 250},
44
+ "HN": {"name": "Honduras", "target": 200},
45
+ "SV": {"name": "El Salvador", "target": 200},
46
+ "NI": {"name": "Nicaragua", "target": 200},
47
+ "CU": {"name": "Cuba", "target": 300}
48
+ }
49
+
50
+ # Initialize the annotation progress data
51
+ for country_code, data in COUNTRY_MAPPING.items():
52
+ annotation_progress[country_code] = {
53
+ "count": 0,
54
+ "percent": 0,
55
+ "name": data["name"],
56
+ "target": data["target"]
57
+ }
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
 
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,
 
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
126
+
127
+ # Update the percentage
128
+ target = annotation_progress[country_code]["target"]
129
+ count = annotation_progress[country_code]["count"]
130
+ percent = min(100, int((count / target) * 100))
131
+ annotation_progress[country_code]["percent"] = percent
132
+
133
+ # Update event queue with progress information
134
+ incoming_events.put({
135
+ "event": "progress_update",
136
+ "country": annotation_progress[country_code]["name"],
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
 
149
  return incoming_events.get()
150
  return {}
151
 
152
+ # Function to get the current annotation progress data
153
+ def get_annotation_progress():
154
+ return json.dumps(annotation_progress)
155
+
156
+ # D3.js map visualization HTML template
157
+ def create_map_html(progress_data):
158
+ return f"""
159
+ <!DOCTYPE html>
160
+ <html>
161
+ <head>
162
+ <script src="https://d3js.org/d3.v7.min.js"></script>
163
+ <script src="https://d3js.org/d3-geo.v2.min.js"></script>
164
+ <script src="https://d3js.org/topojson.v3.min.js"></script>
165
+ <style>
166
+ .country {{
167
+ stroke: #f32b7b;
168
+ stroke-width: 1px;
169
+ }}
170
+ .country:hover {{
171
+ stroke: #4a1942;
172
+ stroke-width: 2px;
173
+ cursor: pointer;
174
+ }}
175
+ .tooltip {{
176
+ position: absolute;
177
+ background-color: rgba(0, 0, 0, 0.8);
178
+ border-radius: 5px;
179
+ padding: 8px;
180
+ color: white;
181
+ font-size: 12px;
182
+ pointer-events: none;
183
+ opacity: 0;
184
+ transition: opacity 0.3s;
185
+ }}
186
+ text {{
187
+ fill: #f1f5f9;
188
+ font-family: sans-serif;
189
+ }}
190
+ .legend-text {{
191
+ fill: #94a3b8;
192
+ font-size: 10px;
193
+ }}
194
+ </style>
195
+ </head>
196
+ <body style="margin:0; background-color:#111;">
197
+ <div id="map-container" style="width:100%; height:600px; position:relative;"></div>
198
+ <div id="tooltip" class="tooltip"></div>
199
+
200
+ <script>
201
+ // The progress data passed from Python
202
+ const progressData = {progress_data};
203
+
204
+ // Set up the SVG container
205
+ const width = document.getElementById('map-container').clientWidth;
206
+ const height = 600;
207
+
208
+ const svg = d3.select("#map-container")
209
+ .append("svg")
210
+ .attr("width", width)
211
+ .attr("height", height)
212
+ .attr("viewBox", `0 0 ${{width}} ${{height}}`)
213
+ .style("background-color", "#111");
214
+
215
+ // Define color scale
216
+ const colorScale = d3.scaleLinear()
217
+ .domain([0, 100])
218
+ .range(["#4a1942", "#f32b7b"]);
219
+
220
+ // Set up projection focused on Latin America and Spain
221
+ const projection = d3.geoMercator()
222
+ .center([-60, 0])
223
+ .scale(width / 5)
224
+ .translate([width / 2, height / 2]);
225
+
226
+ const path = d3.geoPath().projection(projection);
227
+
228
+ // Tooltip setup
229
+ const tooltip = d3.select("#tooltip");
230
+
231
+ // Load the world GeoJSON data
232
+ d3.json("https://raw.githubusercontent.com/holtzy/D3-graph-gallery/master/DATA/world.geojson")
233
+ .then(data => {{
234
+ // Filter for Latin American countries and Spain
235
+ const relevantCountryCodes = Object.keys(progressData);
236
+
237
+ // Draw the map
238
+ svg.selectAll("path")
239
+ .data(data.features)
240
+ .enter()
241
+ .append("path")
242
+ .attr("d", path)
243
+ .attr("class", "country")
244
+ .attr("fill", d => {{
245
+ // Get the ISO code from the properties
246
+ const iso = d.properties.iso_a2;
247
+
248
+ if (progressData[iso]) {{
249
+ return colorScale(progressData[iso].percent);
250
+ }}
251
+ return "#2d3748"; // Default gray for non-tracked countries
252
+ }})
253
+ .on("mouseover", function(event, d) {{
254
+ const iso = d.properties.iso_a2;
255
+
256
+ if (progressData[iso]) {{
257
+ tooltip.style("opacity", 1)
258
+ .html(`
259
+ <strong>${{progressData[iso].name}}</strong><br/>
260
+ Documents: ${{progressData[iso].count.toLocaleString()}}/${{progressData[iso].target.toLocaleString()}}<br/>
261
+ Completion: ${{progressData[iso].percent}}%
262
+ `);
263
+ }}
264
+ }})
265
+ .on("mousemove", function(event) {{
266
+ tooltip.style("left", (event.pageX + 15) + "px")
267
+ .style("top", (event.pageY + 15) + "px");
268
+ }})
269
+ .on("mouseout", function() {{
270
+ tooltip.style("opacity", 0);
271
+ }});
272
+
273
+ // Add legend
274
+ const legendWidth = Math.min(width - 40, 200);
275
+ const legendHeight = 15;
276
+ const legendX = width - legendWidth - 20;
277
+
278
+ const legend = svg.append("g")
279
+ .attr("class", "legend")
280
+ .attr("transform", `translate(${{legendX}}, 30)`);
281
+
282
+ // Create gradient for legend
283
+ const defs = svg.append("defs");
284
+ const gradient = defs.append("linearGradient")
285
+ .attr("id", "dataGradient")
286
+ .attr("x1", "0%")
287
+ .attr("y1", "0%")
288
+ .attr("x2", "100%")
289
+ .attr("y2", "0%");
290
+
291
+ gradient.append("stop")
292
+ .attr("offset", "0%")
293
+ .attr("stop-color", "#4a1942");
294
+
295
+ gradient.append("stop")
296
+ .attr("offset", "100%")
297
+ .attr("stop-color", "#f32b7b");
298
+
299
+ // Add legend title
300
+ legend.append("text")
301
+ .attr("x", legendWidth / 2)
302
+ .attr("y", -10)
303
+ .attr("text-anchor", "middle")
304
+ .attr("font-size", "12px")
305
+ .text("Annotation Progress");
306
+
307
+ // Add legend rectangle
308
+ legend.append("rect")
309
+ .attr("width", legendWidth)
310
+ .attr("height", legendHeight)
311
+ .attr("rx", 2)
312
+ .attr("ry", 2)
313
+ .style("fill", "url(#dataGradient)");
314
+
315
+ // Add legend labels
316
+ legend.append("text")
317
+ .attr("class", "legend-text")
318
+ .attr("x", 0)
319
+ .attr("y", legendHeight + 15)
320
+ .attr("text-anchor", "start")
321
+ .text("0%");
322
+
323
+ legend.append("text")
324
+ .attr("class", "legend-text")
325
+ .attr("x", legendWidth / 2)
326
+ .attr("y", legendHeight + 15)
327
+ .attr("text-anchor", "middle")
328
+ .text("50%");
329
+
330
+ legend.append("text")
331
+ .attr("class", "legend-text")
332
+ .attr("x", legendWidth)
333
+ .attr("y", legendHeight + 15)
334
+ .attr("text-anchor", "end")
335
+ .text("100%");
336
+ }});
337
+
338
+ // Handle window resize
339
+ window.addEventListener('resize', () => {{
340
+ const width = document.getElementById('map-container').clientWidth;
341
+
342
+ // Update SVG dimensions
343
+ d3.select("svg")
344
+ .attr("width", width)
345
+ .attr("viewBox", `0 0 ${{width}} ${{height}}`);
346
+
347
+ // Update projection
348
+ projection.scale(width / 5)
349
+ .translate([width / 2, height / 2]);
350
+
351
+ // Update paths
352
+ d3.selectAll("path").attr("d", path);
353
+
354
+ // Update legend position
355
+ const legendWidth = Math.min(width - 40, 200);
356
+ const legendX = width - legendWidth - 20;
357
+
358
+ d3.select(".legend")
359
+ .attr("transform", `translate(${{legendX}}, 30)`);
360
+ }});
361
+ </script>
362
+ </body>
363
+ </html>
364
+ """
365
+
366
  # Create Gradio interface
367
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="pink", secondary_hue="purple")) as demo:
368
+ argilla_server = client.http_client.base_url if hasattr(client, 'http_client') else "Not connected"
369
+
370
+ with gr.Row():
371
+ gr.Markdown(f"""
372
+ # Argilla Annotation Progress Map
373
+
374
+ ### Connected to Argilla server: {argilla_server}
375
 
376
+ This dashboard visualizes annotation progress across Latin America and Spain.
377
+ The webhook listens for `response.created` events from datasets ending with `_responder_preguntas`.
378
+ """)
379
+
380
+ with gr.Row():
381
+ with gr.Column(scale=2):
382
+ # Map visualization
383
+ map_html = gr.HTML(create_map_html(json.dumps(annotation_progress)), label="Annotation Progress Map")
384
+
385
+ # Stats section
386
+ with gr.Accordion("Overall Statistics", open=False):
387
+ total_docs = gr.Number(value=0, label="Total Documents Collected")
388
+ avg_completion = gr.Number(value=0, label="Average Completion (%)")
389
+ countries_over_50 = gr.Number(value=0, label="Countries Over 50% Complete")
390
 
391
+ with gr.Column(scale=1):
392
+ # Recent events log
393
+ events_json = gr.JSON(label="Recent Events", value={})
394
+
395
+ # Country details
396
+ with gr.Accordion("Country Details", open=True):
397
+ country_selector = gr.Dropdown(
398
+ choices=[f"{data['name']} ({code})" for code, data in COUNTRY_MAPPING.items()],
399
+ label="Select Country"
400
+ )
401
+ country_progress = gr.JSON(label="Country Progress", value={})
402
+
403
+ # Functions to update the UI
404
+ def update_map():
405
+ progress_json = json.dumps(annotation_progress)
406
+ return create_map_html(progress_json)
407
+
408
+ def update_stats():
409
+ total = sum(data["count"] for data in annotation_progress.values())
410
+ percentages = [data["percent"] for data in annotation_progress.values()]
411
+ avg = sum(percentages) / len(percentages) if percentages else 0
412
+ countries_50_plus = sum(1 for p in percentages if p >= 50)
413
 
414
+ return total, avg, countries_50_plus
415
+
416
+ def update_country_details(country_selection):
417
+ if not country_selection:
418
+ return {}
419
+
420
+ # Extract the country code from the selection (format: "Country Name (CODE)")
421
+ code = country_selection.split("(")[-1].replace(")", "").strip()
422
+
423
+ if code in annotation_progress:
424
+ return annotation_progress[code]
425
+ return {}
426
+
427
+ # Set up event handlers
428
+ event_update = gr.on(
429
+ triggers=[read_next_event],
430
+ fn=lambda event: (
431
+ event, # Update events JSON
432
+ update_map() if event.get("event") == "progress_update" else None, # Update map
433
+ update_stats() if event.get("event") == "progress_update" else (None, None, None), # Update stats
434
+ ),
435
+ outputs=[events_json, map_html, total_docs, avg_completion, countries_over_50]
436
+ )
437
+
438
+ country_selector.change(
439
+ fn=update_country_details,
440
+ inputs=[country_selector],
441
+ outputs=[country_progress]
442
+ )
443
+
444
+ # Update everything every 10 seconds
445
+ gr.Timer(10, active=True).tick(
446
+ update_map,
447
+ outputs=[map_html]
448
+ )
449
 
450
  # Mount the Gradio app to the FastAPI server
451
  gr.mount_gradio_app(server, demo, path="/")
 
453
  # Start the FastAPI server
454
  if __name__ == "__main__":
455
  import uvicorn
456
+
457
+ # Initialize with some sample data
458
+ for code in ["MX", "AR", "CO", "ES"]:
459
+ annotation_progress[code]["count"] = int(annotation_progress[code]["target"] * 0.3)
460
+ annotation_progress[code]["percent"] = 30
461
+
462
+ annotation_progress["BR"]["count"] = int(annotation_progress["BR"]["target"] * 0.5)
463
+ annotation_progress["BR"]["percent"] = 50
464
+
465
+ annotation_progress["CL"]["count"] = int(annotation_progress["CL"]["target"] * 0.7)
466
+ annotation_progress["CL"]["percent"] = 70
467
+
468
+ # Start the server
469
  uvicorn.run(server, host="0.0.0.0", port=7860)