jeremyarancio's picture
fix: :zap: Handle Server timeout errors + Add whitespace additions and deletions in text diff
161967b
from typing import Dict, Tuple, Optional, TypeAlias
import requests
import base64
import json
from dataclasses import dataclass
from utils import get_logger, LANGS
from openfoodfacts.api import ProductResource, APIConfig
import gradio as gr
logger = get_logger()
BASE_URL = "https://robotoff.openfoodfacts.org/api/v1" # Prod
# BASE_URL = "http://localhost:5500/api/v1" # Dev
UPDATE = 1
Annotation: TypeAlias = Tuple[str, str, str, str, str]
@dataclass
class Authentification:
username: str
password: str
def get_credentials(self) -> str:
credentials = f"{self.username}:{self.password}"
return base64.b64encode(credentials.encode()).decode()
def next_annotation(lang: Optional[str]) -> Annotation:
lang_key = LANGS.get(lang)
if not lang_key:
logger.warning("The provided Lang was not found. 'All' returned by default.")
insight = import_random_insight(lang_key=lang_key)
logger.info("Imported insight: %s", insight)
return (
insight["id"],
insight["data"]["original"],
insight["data"]["correction"], # Saved for comparison with annotator changes
insight["data"]["correction"],
get_image_url(insight["barcode"]),
get_product_url(insight["barcode"]),
)
def submit_correction(
insight_id: str,
annotator_correction: str,
model_correction: str,
username: str,
password: str,
lang: str,
update: bool = UPDATE,
):
auth = Authentification(username, password)
correction = (
annotator_correction if annotator_correction != model_correction else None
)
try:
submit_to_product_opener(
insight_id=insight_id,
update=update,
annotator_correction=correction,
auth=auth,
)
except gr.Error as e:
gr.Warning(e.message) # We use gr.Warning instead of gr.Error to keep the flow
return (
gr.update(),
gr.update(),
gr.update(),
gr.update(),
gr.update(),
gr.update(),
) # Stay unchanged
gr.Info("Product successfuly updated. Many thanks!")
return next_annotation(lang=lang)
def import_random_insight(
insight_type: str = "ingredient_spellcheck",
predictor: str = "fine-tuned-mistral-7b",
lang_key: Optional[str] = None,
) -> Dict:
url = (
f"{BASE_URL}/insights/random?count=1&type={insight_type}&predictor={predictor}"
)
if lang_key:
url += f"&value_tag={lang_key}"
try:
response = requests.get(url)
response.raise_for_status() # Raises an HTTPError for bad responses (4xx or 5xx)
data = response.json()
if "insights" in data and data["insights"]:
return data["insights"][0]
else:
gr.Warning("No insights found; fetching default insight instead.")
return import_random_insight(insight_type, predictor)
except requests.ReadTimeout:
gr.Error("There's an issue with the server... Wait a couple of minutes and try again.")
except requests.RequestException as e:
gr.Error(f"Import product from Product Opener failed: {e}")
def submit_to_product_opener(
insight_id: str,
update: bool,
auth: Authentification,
annotator_correction: Optional[str] = None,
) -> None:
url = f"{BASE_URL}/insights/annotate"
headers = {
"Authorization": f"Basic {auth.get_credentials()}",
"Content-Type": "application/x-www-form-urlencoded",
}
if annotator_correction:
logger.info(
"Change from annotator. New insight sent to Product Opener. New correction: %s",
annotator_correction,
)
payload = {
"insight_id": insight_id,
"annotation": 2,
"update": update,
"data": json.dumps({"annotation": annotator_correction}),
}
else:
logger.info(
"No change from annotator. Original insight sent to Product Opener."
)
payload = {
"insight_id": insight_id,
"annotation": 1,
"update": update,
}
try:
response = requests.post(url, data=payload, headers=headers)
response.raise_for_status()
except requests.ReadTimeout:
gr.Error("There's an issue with the server... Wait a couple of minutes and try again.")
except requests.RequestException as e:
logger.error(e)
logger.error(response.content)
raise gr.Error(
"Failed to submit to Product Opener. Are your username and password correct?"
)
def get_image_url(
code: str,
user_agent: str = "Spellcheck-Annotate",
) -> str:
fields = ["image_ingredients_url"]
data = ProductResource(api_config=APIConfig(user_agent=user_agent)).get(
code=code,
fields=fields,
)
image_url = data.get(fields[0])
return image_url
def enable_buttons(username, password):
# Return the updated button states: interactive if both username and password are filled
state = bool(username) and bool(password)
return gr.update(interactive=state), gr.update(interactive=state)
def get_product_url(barcode: int) -> str:
return f"https://world.openfoodfacts.org/product/{barcode}"