jeremyarancio commited on
Commit
0c7edbd
·
1 Parent(s): d008f82

feat: :art: Add guidelines + Product page

Browse files
Files changed (2) hide show
  1. app.py +41 -25
  2. back_end.py +33 -15
app.py CHANGED
@@ -4,18 +4,23 @@ from back_end import next_annotation, submit_correction, enable_buttons
4
  from utils import diff_texts, LANGS
5
 
6
 
7
- # Gradio Interface
8
  with gr.Blocks() as demo:
9
  gr.Markdown(
10
  """# Ingredients Spellcheck 🍊
11
- ### Review the Spellcheck corrections.\n
12
- Your precious feedback will be integrated to the Open Food Facts database!
13
-
14
- **Important:** Authenticate yourself using your Open Food Facts username and password to add modifications to a product.
15
- If you're not registered yet, you can do it [here](https://world.openfoodfacts.org/cgi/user.pl)!
 
 
 
 
 
16
  """
17
  )
18
-
19
  with gr.Row():
20
  with gr.Column():
21
  insight_id = gr.Textbox(
@@ -28,15 +33,14 @@ with gr.Blocks() as demo:
28
  label="OFF Username",
29
  )
30
  off_password = gr.Textbox(
31
- label="OFF Password",
32
- type="password",
33
  )
34
  lang = gr.Dropdown(
35
  choices=LANGS.keys(),
36
  value="All",
37
- label="Language",
38
  info="Optional: Filter a language.",
39
-
40
  )
41
 
42
  # Saved to detect change from annotator
@@ -46,16 +50,16 @@ with gr.Blocks() as demo:
46
  label="Original Text (Uneditable)",
47
  info="This is the original text.",
48
  interactive=False, # Make this text box uneditable
49
- lines=3
50
  )
51
-
52
  annotator_correction = gr.Textbox(
53
  label="Corrected Text (Editable)",
54
  info="This is the AI-corrected text. You can modify it.",
55
  interactive=True, # Make this text box editable
56
  lines=3,
57
  )
58
-
59
  # Diff Display using HighlightedText
60
  diff_display = gr.HighlightedText(
61
  label="Difference Between Original and Corrected Text",
@@ -64,25 +68,36 @@ with gr.Blocks() as demo:
64
  color_map={"+": "green", "-": "red"},
65
  )
66
 
67
- image = gr.Image()
68
-
 
 
69
  # Validate button to move to next annotation
70
  with gr.Row():
71
  validate_button = gr.Button("Validate", interactive=False)
72
  skip_button = gr.Button("Skip", interactive=False)
73
-
 
 
74
  validate_button.click(
75
  submit_correction,
76
- inputs=[insight_id, annotator_correction, model_correction, off_username, off_password, lang],
77
- outputs=[insight_id, original, model_correction, annotator_correction, image],
 
 
 
 
 
 
 
78
  )
79
 
80
  skip_button.click(
81
  next_annotation,
82
  inputs=[lang],
83
- outputs=[insight_id, original, model_correction, annotator_correction, image],
84
  )
85
-
86
  annotator_correction.change(
87
  diff_texts, # Call diff function
88
  inputs=[original, annotator_correction],
@@ -90,11 +105,11 @@ with gr.Blocks() as demo:
90
  )
91
 
92
  off_username.change(
93
- enable_buttons,
94
- inputs=[off_username, off_password],
95
  outputs=[validate_button, skip_button],
96
  )
97
-
98
  off_password.change(
99
  enable_buttons,
100
  inputs=[off_username, off_password],
@@ -104,7 +119,7 @@ with gr.Blocks() as demo:
104
  lang.change(
105
  next_annotation,
106
  inputs=[lang],
107
- outputs=[insight_id, original, model_correction, annotator_correction, image],
108
  )
109
 
110
  # Load the first set of texts when the demo starts
@@ -117,6 +132,7 @@ with gr.Blocks() as demo:
117
  model_correction,
118
  annotator_correction,
119
  image,
 
120
  ],
121
  )
122
 
 
4
  from utils import diff_texts, LANGS
5
 
6
 
7
+ ## COMPONENTS
8
  with gr.Blocks() as demo:
9
  gr.Markdown(
10
  """# Ingredients Spellcheck 🍊
11
+ ### Review the Spellcheck corrections. Your precious feedback will be integrated to the Open Food Facts database!
12
+
13
+ ### Instructions:
14
+ * *You are provided the original list of ingredients text as stored in the Open Food Facts (OFF) database, the Spellcheck prediction, and optionally a picture of the product.*
15
+ * *Your task, if you accept 💣, is to review the Spellcheck prediction by either validating or correcting it.*
16
+ * *The picture is only here to help you during the annotation as a reference. It can happen that the language of the text and the picture are different. **Keep calm and focus on the text.***
17
+ * *It can happen that the Producer has made a mistake on the product packaging. Since we parse the list of ingredients to extract its information, it would be preferable if you fix the typo.*
18
+
19
+ ### Important:
20
+ * Authenticate yourself using your Open Food Facts username and password to add modifications to a product. If you're not registered yet, you can do so [here](https://world.openfoodfacts.org/cgi/user.pl)!
21
  """
22
  )
23
+
24
  with gr.Row():
25
  with gr.Column():
26
  insight_id = gr.Textbox(
 
33
  label="OFF Username",
34
  )
35
  off_password = gr.Textbox(
36
+ label="OFF Password",
37
+ type="password",
38
  )
39
  lang = gr.Dropdown(
40
  choices=LANGS.keys(),
41
  value="All",
42
+ label="Language",
43
  info="Optional: Filter a language.",
 
44
  )
45
 
46
  # Saved to detect change from annotator
 
50
  label="Original Text (Uneditable)",
51
  info="This is the original text.",
52
  interactive=False, # Make this text box uneditable
53
+ lines=3,
54
  )
55
+
56
  annotator_correction = gr.Textbox(
57
  label="Corrected Text (Editable)",
58
  info="This is the AI-corrected text. You can modify it.",
59
  interactive=True, # Make this text box editable
60
  lines=3,
61
  )
62
+
63
  # Diff Display using HighlightedText
64
  diff_display = gr.HighlightedText(
65
  label="Difference Between Original and Corrected Text",
 
68
  color_map={"+": "green", "-": "red"},
69
  )
70
 
71
+ with gr.Column():
72
+ url = gr.Textbox(label="Product page", show_copy_button=True)
73
+ image = gr.Image()
74
+
75
  # Validate button to move to next annotation
76
  with gr.Row():
77
  validate_button = gr.Button("Validate", interactive=False)
78
  skip_button = gr.Button("Skip", interactive=False)
79
+
80
+
81
+ ## COMPONENT INTERACTIONS
82
  validate_button.click(
83
  submit_correction,
84
+ inputs=[
85
+ insight_id,
86
+ annotator_correction,
87
+ model_correction,
88
+ off_username,
89
+ off_password,
90
+ lang,
91
+ ],
92
+ outputs=[insight_id, original, model_correction, annotator_correction, image, url],
93
  )
94
 
95
  skip_button.click(
96
  next_annotation,
97
  inputs=[lang],
98
+ outputs=[insight_id, original, model_correction, annotator_correction, image, url],
99
  )
100
+
101
  annotator_correction.change(
102
  diff_texts, # Call diff function
103
  inputs=[original, annotator_correction],
 
105
  )
106
 
107
  off_username.change(
108
+ enable_buttons,
109
+ inputs=[off_username, off_password],
110
  outputs=[validate_button, skip_button],
111
  )
112
+
113
  off_password.change(
114
  enable_buttons,
115
  inputs=[off_username, off_password],
 
119
  lang.change(
120
  next_annotation,
121
  inputs=[lang],
122
+ outputs=[insight_id, original, model_correction, annotator_correction, image, url],
123
  )
124
 
125
  # Load the first set of texts when the demo starts
 
132
  model_correction,
133
  annotator_correction,
134
  image,
135
+ url,
136
  ],
137
  )
138
 
back_end.py CHANGED
@@ -12,7 +12,7 @@ import gradio as gr
12
 
13
  logger = get_logger()
14
 
15
- BASE_URL = "https://robotoff.openfoodfacts.org/api/v1" # Prod
16
  # BASE_URL = "http://localhost:5500/api/v1" # Dev
17
  UPDATE = 1
18
 
@@ -38,9 +38,10 @@ def next_annotation(lang: Optional[str]) -> Annotation:
38
  return (
39
  insight["id"],
40
  insight["data"]["original"],
41
- insight["data"]["correction"], # Saved for comparison with annotator changes
42
  insight["data"]["correction"],
43
- get_image_url(insight["barcode"])
 
44
  )
45
 
46
 
@@ -50,10 +51,12 @@ def submit_correction(
50
  model_correction: str,
51
  username: str,
52
  password: str,
53
- update: bool = UPDATE
54
  ):
55
  auth = Authentification(username, password)
56
- correction = annotator_correction if annotator_correction != model_correction else None
 
 
57
  try:
58
  submit_to_product_opener(
59
  insight_id=insight_id,
@@ -62,14 +65,15 @@ def submit_correction(
62
  auth=auth,
63
  )
64
  except gr.Error as e:
65
- gr.Warning(e.message) # We use gr.Warning instead of gr.Error to keep the flow
66
  return (
67
  gr.update(),
68
  gr.update(),
69
  gr.update(),
70
  gr.update(),
71
  gr.update(),
72
- ) # Stay unchanged
 
73
  gr.Info("Product successfuly updated. Many thanks!")
74
  return next_annotation()
75
 
@@ -77,9 +81,11 @@ def submit_correction(
77
  def import_random_insight(
78
  insight_type: str = "ingredient_spellcheck",
79
  predictor: str = "fine-tuned-mistral-7b",
80
- lang_key: Optional[str] = None
81
  ) -> Dict:
82
- url = f"{BASE_URL}/insights/random?count=1&type={insight_type}&predictor={predictor}"
 
 
83
  if lang_key:
84
  url += f"&value_tag={lang_key}"
85
  try:
@@ -93,7 +99,7 @@ def import_random_insight(
93
  return import_random_insight(insight_type, predictor)
94
  except requests.RequestException as e:
95
  gr.Error(f"Import product from Product Opener failed: {e}")
96
-
97
 
98
  def submit_to_product_opener(
99
  insight_id: str,
@@ -104,10 +110,13 @@ def submit_to_product_opener(
104
  url = f"{BASE_URL}/insights/annotate"
105
  headers = {
106
  "Authorization": f"Basic {auth.get_credentials()}",
107
- 'Content-Type': 'application/x-www-form-urlencoded',
108
  }
109
  if annotator_correction:
110
- logger.info("Change from annotator. New insight sent to Product Opener. New correction: %s", annotator_correction)
 
 
 
111
  payload = {
112
  "insight_id": insight_id,
113
  "annotation": 2,
@@ -115,7 +124,9 @@ def submit_to_product_opener(
115
  "data": json.dumps({"annotation": annotator_correction}),
116
  }
117
  else:
118
- logger.info("No change from annotator. Original insight sent to Product Opener.")
 
 
119
  payload = {
120
  "insight_id": insight_id,
121
  "annotation": 1,
@@ -127,8 +138,10 @@ def submit_to_product_opener(
127
  except requests.RequestException as e:
128
  logger.error(e)
129
  logger.error(response.content)
130
- raise gr.Error("Failed to submit to Product Opener. Are your username and password correct?")
131
-
 
 
132
 
133
  def get_image_url(
134
  code: str,
@@ -147,3 +160,8 @@ def enable_buttons(username, password):
147
  # Return the updated button states: interactive if both username and password are filled
148
  state = bool(username) and bool(password)
149
  return gr.update(interactive=state), gr.update(interactive=state)
 
 
 
 
 
 
12
 
13
  logger = get_logger()
14
 
15
+ BASE_URL = "https://robotoff.openfoodfacts.org/api/v1" # Prod
16
  # BASE_URL = "http://localhost:5500/api/v1" # Dev
17
  UPDATE = 1
18
 
 
38
  return (
39
  insight["id"],
40
  insight["data"]["original"],
41
+ insight["data"]["correction"], # Saved for comparison with annotator changes
42
  insight["data"]["correction"],
43
+ get_image_url(insight["barcode"]),
44
+ get_product_url(insight["barcode"]),
45
  )
46
 
47
 
 
51
  model_correction: str,
52
  username: str,
53
  password: str,
54
+ update: bool = UPDATE,
55
  ):
56
  auth = Authentification(username, password)
57
+ correction = (
58
+ annotator_correction if annotator_correction != model_correction else None
59
+ )
60
  try:
61
  submit_to_product_opener(
62
  insight_id=insight_id,
 
65
  auth=auth,
66
  )
67
  except gr.Error as e:
68
+ gr.Warning(e.message) # We use gr.Warning instead of gr.Error to keep the flow
69
  return (
70
  gr.update(),
71
  gr.update(),
72
  gr.update(),
73
  gr.update(),
74
  gr.update(),
75
+ gr.update(),
76
+ ) # Stay unchanged
77
  gr.Info("Product successfuly updated. Many thanks!")
78
  return next_annotation()
79
 
 
81
  def import_random_insight(
82
  insight_type: str = "ingredient_spellcheck",
83
  predictor: str = "fine-tuned-mistral-7b",
84
+ lang_key: Optional[str] = None,
85
  ) -> Dict:
86
+ url = (
87
+ f"{BASE_URL}/insights/random?count=1&type={insight_type}&predictor={predictor}"
88
+ )
89
  if lang_key:
90
  url += f"&value_tag={lang_key}"
91
  try:
 
99
  return import_random_insight(insight_type, predictor)
100
  except requests.RequestException as e:
101
  gr.Error(f"Import product from Product Opener failed: {e}")
102
+
103
 
104
  def submit_to_product_opener(
105
  insight_id: str,
 
110
  url = f"{BASE_URL}/insights/annotate"
111
  headers = {
112
  "Authorization": f"Basic {auth.get_credentials()}",
113
+ "Content-Type": "application/x-www-form-urlencoded",
114
  }
115
  if annotator_correction:
116
+ logger.info(
117
+ "Change from annotator. New insight sent to Product Opener. New correction: %s",
118
+ annotator_correction,
119
+ )
120
  payload = {
121
  "insight_id": insight_id,
122
  "annotation": 2,
 
124
  "data": json.dumps({"annotation": annotator_correction}),
125
  }
126
  else:
127
+ logger.info(
128
+ "No change from annotator. Original insight sent to Product Opener."
129
+ )
130
  payload = {
131
  "insight_id": insight_id,
132
  "annotation": 1,
 
138
  except requests.RequestException as e:
139
  logger.error(e)
140
  logger.error(response.content)
141
+ raise gr.Error(
142
+ "Failed to submit to Product Opener. Are your username and password correct?"
143
+ )
144
+
145
 
146
  def get_image_url(
147
  code: str,
 
160
  # Return the updated button states: interactive if both username and password are filled
161
  state = bool(username) and bool(password)
162
  return gr.update(interactive=state), gr.update(interactive=state)
163
+
164
+
165
+ def get_product_url(barcode: int) -> str:
166
+ return f"https://world.openfoodfacts.org/product/{barcode}"
167
+