from __future__ import annotations import json from datetime import datetime, timezone from pathlib import Path from typing import Any, Dict import requests LOCAL_FEEDBACK_PATH = Path(__file__).parent / "misc" / "feedback_submissions.jsonl" class FeedbackStore: def __init__( self, supabase_url: str = "", supabase_anon_key: str = "", supabase_service_key: str = "", table_name: str = "feedback_submissions", ) -> None: self.supabase_url = supabase_url.rstrip("/") self.supabase_anon_key = supabase_anon_key self.supabase_service_key = supabase_service_key self.table_name = table_name @property def is_remote_enabled(self) -> bool: return bool(self.supabase_url and (self.supabase_service_key or self.supabase_anon_key)) @property def backend_label(self) -> str: if self.is_remote_enabled: return "Supabase" return f"Local file ({LOCAL_FEEDBACK_PATH.name})" def save_submission( self, input_sentence: str, original_output: str, corrected_output: str, user_comment: str = "", decode_mode: str = "", ) -> Dict[str, Any]: payload = { "created_at": datetime.now(timezone.utc).isoformat(), "input_sentence": input_sentence, "original_output": original_output, "corrected_output": corrected_output, "user_comment": user_comment.strip(), "decode_mode": decode_mode, "review_status": "pending", "admin_notes": "", "source": "streamlit", } if self.is_remote_enabled: return self._insert_remote(payload) return self._insert_local(payload) def _insert_local(self, payload: Dict[str, Any]) -> Dict[str, Any]: LOCAL_FEEDBACK_PATH.parent.mkdir(parents=True, exist_ok=True) with LOCAL_FEEDBACK_PATH.open("a", encoding="utf-8") as f: f.write(json.dumps(payload, ensure_ascii=False) + "\n") return {"ok": True, "record": payload} def _insert_remote(self, payload: Dict[str, Any]) -> Dict[str, Any]: url = f"{self.supabase_url}/rest/v1/{self.table_name}" response = requests.post( url, headers=self._headers(admin=False, prefer="return=representation"), json=payload, timeout=15, ) response.raise_for_status() rows = response.json() row = rows[0] if rows else payload return {"ok": True, "record": row} def _headers(self, admin: bool, prefer: str = "") -> Dict[str, str]: key = self.supabase_service_key if admin and self.supabase_service_key else self.supabase_anon_key or self.supabase_service_key headers = { "apikey": key, "Authorization": f"Bearer {key}", "Content-Type": "application/json", } if prefer: headers["Prefer"] = prefer return headers def format_feedback_error(exc: Exception) -> str: if isinstance(exc, requests.HTTPError) and exc.response is not None: try: payload = exc.response.json() if isinstance(payload, dict): message = payload.get("message") or payload.get("hint") or json.dumps(payload) return f"{exc.response.status_code}: {message}" except ValueError: pass return f"{exc.response.status_code}: {exc.response.text.strip()}" return str(exc)