Spaces:
Sleeping
Sleeping
| """ | |
| Hugging Science Feedback API | |
| A minimal FastAPI app that accepts feedback submissions and appends them | |
| to the hugging-science/feedback HF dataset. | |
| Deploy as a HF Space (Docker SDK): | |
| hugging-science/feedback-api | |
| Required Space secret: | |
| HF_TOKEN β a write-scoped token for the hugging-science org | |
| """ | |
| import os | |
| import json | |
| import uuid | |
| from datetime import datetime, timezone | |
| from typing import Optional | |
| from fastapi import FastAPI, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel, field_validator | |
| from huggingface_hub import HfApi, hf_hub_download | |
| import tempfile | |
| # ββ Config ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| DATASET_REPO = "hugging-science/feedback" | |
| FEEDBACK_FILE = "feedback.jsonl" | |
| HF_TOKEN = os.environ.get("HF_TOKEN") | |
| if not HF_TOKEN: | |
| raise RuntimeError("HF_TOKEN secret is not set") | |
| api = HfApi(token=HF_TOKEN) | |
| # ββ App βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| app = FastAPI(title="Hugging Science Feedback API", version="1.0.0") | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["https://huggingscience.co", "http://localhost:5173"], | |
| allow_methods=["POST", "GET"], | |
| allow_headers=["Content-Type"], | |
| ) | |
| # ββ Schema ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| VALID_TYPES = {"dataset", "model", "challenge", "feedback"} | |
| class FeedbackItem(BaseModel): | |
| type: str | |
| title: Optional[str] = None | |
| description: str | |
| submitted_at: Optional[str] = None | |
| source: Optional[str] = "huggingscience.co" | |
| def validate_type(cls, v): | |
| if v not in VALID_TYPES: | |
| raise ValueError(f"type must be one of {VALID_TYPES}") | |
| return v | |
| def validate_description(cls, v): | |
| v = v.strip() | |
| if len(v) < 5: | |
| raise ValueError("description must be at least 5 characters") | |
| if len(v) > 2000: | |
| raise ValueError("description must be under 2000 characters") | |
| return v | |
| # ββ Helpers βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def load_existing() -> list[dict]: | |
| """Download the current feedback.jsonl from the dataset, return as list.""" | |
| try: | |
| path = hf_hub_download( | |
| repo_id=DATASET_REPO, | |
| filename=FEEDBACK_FILE, | |
| repo_type="dataset", | |
| token=HF_TOKEN, | |
| ) | |
| with open(path) as f: | |
| return [json.loads(line) for line in f if line.strip()] | |
| except Exception: | |
| # File doesn't exist yet β start fresh | |
| return [] | |
| def save_feedback(rows: list[dict]) -> None: | |
| """Upload the full feedback.jsonl back to the dataset.""" | |
| content = "\n".join(json.dumps(r, ensure_ascii=False) for r in rows) + "\n" | |
| with tempfile.NamedTemporaryFile(mode="w", suffix=".jsonl", delete=False) as f: | |
| f.write(content) | |
| tmp_path = f.name | |
| api.upload_file( | |
| path_or_fileobj=tmp_path, | |
| path_in_repo=FEEDBACK_FILE, | |
| repo_id=DATASET_REPO, | |
| repo_type="dataset", | |
| commit_message=f"Add feedback entry ({rows[-1]['id'][:8]})", | |
| ) | |
| # ββ Routes ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def root(): | |
| return {"status": "ok", "service": "Hugging Science Feedback API"} | |
| def submit_feedback(item: FeedbackItem): | |
| entry = { | |
| "id": str(uuid.uuid4()), | |
| "type": item.type, | |
| "title": item.title or "", | |
| "description": item.description, | |
| "submitted_at": item.submitted_at or datetime.now(timezone.utc).isoformat(), | |
| "source": item.source or "huggingscience.co", | |
| "status": "pending", | |
| } | |
| try: | |
| rows = load_existing() | |
| rows.append(entry) | |
| save_feedback(rows) | |
| except Exception as e: | |
| raise HTTPException(status_code=500, detail=f"Failed to save feedback: {e}") | |
| return {"ok": True, "id": entry["id"]} | |