simikkk's picture
Upload 36 files
16e1aa7 verified
|
Raw
History Blame Contribute Delete
6.55 kB

A newer version of the Gradio SDK is available: 6.22.0

Upgrade
metadata
title: Etsy Listing Optimizer
emoji: 🏷️
colorFrom: yellow
colorTo: pink
sdk: gradio
sdk_version: 5.12.0
app_file: server.py
pinned: false
license: mit

Etsy Listing Optimizer

A micro-SaaS tool that generates SEO-optimized Etsy listing titles, tags, and descriptions from a plain-language product description — with every AI output programmatically validated (character limits, exact tag count, zero duplicate words, no truncation) before it's ever shown to a user.

Built on an entirely free tier: Gradio + FastAPI on Hugging Face Spaces, Supabase for auth/database, Groq (with Gemini fallback) for AI generation, Stripe for billing.

New here? Read DEPLOY.md first — it's the step-by-step guide to getting every account/API key and going live. This README is the map of what's in the codebase.

Quickstart (local, no API keys needed)

The app runs in demo mode — in-memory storage, a deterministic mock AI provider — whenever real credentials aren't configured, so you can try the whole flow before signing up for anything.

pip install -r requirements.txt
uvicorn app.main:app --reload --port 7860

Open http://localhost:7860 — sign up with any email/password (10+ chars, letters + numbers), generate a listing, poke around the tabs. Data resets every time you restart the process.

To go live with real accounts/AI/payments, follow DEPLOY.md.

Project structure

server.py                    HF Spaces (Gradio SDK) entry point - runs the app on port 7860
app/
  main.py                  FastAPI app: wires up all routers + mounts Gradio UI
  ui.py                    Gradio Blocks UI (calls the FastAPI routes in-process)
  config.py                Tier/pricing definitions, feature flags, demo-mode detection
  security.py               Input sanitization / prompt-injection hygiene

  llm/
    prompts.py              System prompt + few-shot examples (edit wording here)
    provider.py              Groq / Gemini / Mock provider abstraction + fallback
    validation.py             Programmatic validation of generated output
    generator.py               Orchestrates generate -> validate -> retry -> critic pass

  auth/
    service.py               Supabase Auth wrapper + in-memory demo auth
    deps.py                    FastAPI dependency: resolve current user from Bearer token

  db/
    store.py                  Supabase-backed store + in-memory demo store (same interface)

  payments/
    stripe_service.py          Checkout session creation + webhook verification/handling

  routes/
    auth_routes.py             /auth/signup, /auth/login, /auth/logout, /auth/password-reset
    generate.py                 /generate — the core feature, tier/quota-gated
    brand_voice.py               /brand-voice CRUD
    history.py                    /history, /history/export.csv
    account.py                     /account/export, /account/delete (GDPR)
    payments.py                     /billing/checkout/{tier}, /billing/webhook
    bulk.py                          /bulk/generate (Business tier, CSV upload)

  legal/
    pages.py                  /legal/privacy, /legal/terms, /legal/refunds (draft text)

  middleware/
    rate_limit.py             Per-IP (auth) and per-account (/generate) rate limits

tests/
  test_validation.py        Unit tests for the validation logic (run: pytest tests/ -q)

supabase_schema.sql          Run this in the Supabase SQL editor - tables + RLS policies
requirements.txt             Pinned dependency versions
.env.example                 Every environment variable, with a comment on where to get it
DEPLOY.md                     Step-by-step deployment + API key walkthrough

How generation is kept honest

/generate never trusts the model's output at face value:

  1. llm/generator.py calls the LLM (Groq, falling back to Gemini on a 429).
  2. llm/validation.py checks, in code: exact tag count (13), character limits (140 for titles, 20 for tags), no duplicate words across tags/title, no mid-word/mid-sentence truncation, and the API's own finish_reason isn't "length" (which would mean the response was cut off by the token limit).
  3. If anything fails, the generator re-prompts the model with the specific violation, up to 2 retries.
  4. Once formally valid, a second cheap "critic" LLM call checks whether each tag is actually relevant to the submitted product (catches hallucinated tags) — if any are flagged, only those specific tags are regenerated, not the whole listing.

All of this is exercised by tests/test_validation.py and was manually regression-tested end-to-end (signup, generation, tier gating, quota enforcement, rate limiting, GDPR export/delete, Stripe error handling) before being handed off.

Tiers

Defined in one place, app/config.py — nowhere else in the codebase hardcodes a limit or a price. See the table there (or the Account tab in the UI) for the current Free/Starter/Pro/Business feature matrix.

Security & compliance notes

  • Passwords never touch application code — Supabase Auth handles hashing (bcrypt); the demo-mode fallback used when Supabase isn't configured hashes with a salt purely as a sandbox safeguard and is explicitly not production-grade (see the warning in auth/service.py).
  • Row Level Security is enabled on every table in supabase_schema.sql.
  • Every generation request is checked against the monthly quota before calling the LLM, so rejected requests never burn API spend.
  • Stripe webhooks are rejected outright if signature verification fails.
  • /account/delete removes both the database rows and the underlying auth user/session — a deleted account's token stops working immediately.
  • See DEPLOY.md for the GDPR / legal-pages disclaimer: the shipped Privacy Policy / Terms / Refund Policy are a starting checklist, not legal advice — have them reviewed before real customers sign up.

Known v1 limitations (by design, per the "keep it simple" brief)

  • Rate limiting is in-memory (slowapi) — fine for a single HF Space instance, won't work correctly if you scale to multiple instances without switching to a Redis-backed store.
  • Bulk generation (/bulk/generate) processes rows synchronously with a 25-row cap per request — adequate for a v1 CPU-tier deployment, not meant for large batch jobs.
  • Resend (transactional email) is stubbed, not wired up — Supabase's default auth emails cover signup confirmation/password reset for free in the meantime.