example-projects/examples/everything_function/web/main.py

196 lines
5.7 KiB
Python

"""FastAPI backend for the everything_function web UI.
This server doesn't do any AI work itself. It imports the same `ai_xxx`
functions used by the terminal demo scripts and exposes them as HTTP
endpoints so a browser can call them. The point: the web UI is just
another front-end for the *same Python functions* you can run from
`scripts/`. Nothing magic is happening in here.
"""
from __future__ import annotations
import sys
import tempfile
from pathlib import Path
from fastapi import FastAPI, File, Form, HTTPException, UploadFile
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
SCRIPTS_DIR = Path("/app/scripts")
SAMPLES_DIR = Path("/app/sample_images")
sys.path.insert(0, str(SCRIPTS_DIR))
import action_list # noqa: E402
import algebra_roots # noqa: E402
import arithmetic # noqa: E402
import image_label # noqa: E402
import ocr # noqa: E402
import prime_factorization # noqa: E402
import recipe_from_food # noqa: E402
import sentiment # noqa: E402
import summarize # noqa: E402
import translate # noqa: E402
app = FastAPI(title="everything_function")
# ------ arithmetic ------------------------------------------------------------
class ArithRequest(BaseModel):
op: str
a: float
b: float
@app.post("/api/arithmetic")
def api_arithmetic(req: ArithRequest):
if req.op not in arithmetic.OPS:
raise HTTPException(400, f"unknown op: {req.op!r}")
ai_fn, py_fn = arithmetic.OPS[req.op]
ai_result = ai_fn(req.a, req.b).strip().rstrip(".")
py_result = py_fn(req.a, req.b)
return {"ai": ai_result, "python": str(py_result)}
# ------ algebra ---------------------------------------------------------------
class AlgebraRequest(BaseModel):
coefficients: list[float]
@app.post("/api/algebra")
def api_algebra(req: AlgebraRequest):
if len(req.coefficients) < 2:
raise HTTPException(400, "need at least 2 coefficients")
polynomial = algebra_roots.format_polynomial(req.coefficients)
ai_result = algebra_roots.ai_polynomial_roots(req.coefficients)
py_result = algebra_roots.py_polynomial_roots(req.coefficients)
return {"polynomial": polynomial, "ai": ai_result, "python": py_result}
# ------ prime factorization ---------------------------------------------------
class PrimeRequest(BaseModel):
n: int
@app.post("/api/prime_factorization")
def api_prime(req: PrimeRequest):
ai_result = prime_factorization.ai_prime_factorize(req.n).strip().rstrip(".")
py_result = prime_factorization.py_prime_factorize(req.n)
return {"ai": ai_result, "python": py_result}
# ------ text demos ------------------------------------------------------------
class SentimentRequest(BaseModel):
text: str
@app.post("/api/sentiment")
def api_sentiment(req: SentimentRequest):
return {"result": sentiment.ai_sentiment(req.text)}
class TranslateRequest(BaseModel):
text: str
target_language: str
@app.post("/api/translate")
def api_translate(req: TranslateRequest):
return {"result": translate.ai_translate(req.text, req.target_language)}
class SummarizeRequest(BaseModel):
text: str
max_words: int = 30
@app.post("/api/summarize")
def api_summarize(req: SummarizeRequest):
return {"result": summarize.ai_summarize(req.text, max_words=req.max_words)}
class ActionListRequest(BaseModel):
description: str
@app.post("/api/action_list")
def api_action_list(req: ActionListRequest):
return {"result": "- " + action_list.ai_action_list(req.description)}
# ------ image demos -----------------------------------------------------------
def _resolve_image(file: UploadFile | None, sample: str | None) -> Path:
"""Save the upload to /tmp, or return the named sample image's path."""
if file is not None and file.filename:
suffix = Path(file.filename).suffix.lower() or ".jpg"
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=suffix)
try:
tmp.write(file.file.read())
return Path(tmp.name)
finally:
tmp.close()
if sample:
path = SAMPLES_DIR / sample
if not path.exists():
raise HTTPException(404, f"sample image not found: {sample!r}")
return path
raise HTTPException(400, "must provide either an uploaded file or a sample name")
@app.post("/api/image_label")
def api_image_label(
file: UploadFile | None = File(None),
sample: str | None = Form(None),
):
path = _resolve_image(file, sample)
return {"result": image_label.ai_image_label(path)}
@app.post("/api/recipe_from_food")
def api_recipe(
file: UploadFile | None = File(None),
sample: str | None = Form(None),
):
path = _resolve_image(file, sample)
return {"result": recipe_from_food.ai_recipe_from_food(path)}
@app.post("/api/ocr")
def api_ocr(
file: UploadFile | None = File(None),
sample: str | None = Form(None),
):
path = _resolve_image(file, sample)
return {"result": ocr.ai_ocr(path)}
# ------ sample images ---------------------------------------------------------
@app.get("/api/sample_images")
def list_samples():
return [
{"name": p.name, "url": f"/api/sample_images/{p.name}"}
for p in sorted(SAMPLES_DIR.glob("*.jpg"))
]
@app.get("/api/sample_images/{name}")
def get_sample(name: str):
path = SAMPLES_DIR / name
if path.suffix.lower() not in {".jpg", ".jpeg", ".png"} or not path.exists():
raise HTTPException(404)
# Pin to the samples directory so we can't escape via "../" tricks.
if path.resolve().parent != SAMPLES_DIR.resolve():
raise HTTPException(404)
return FileResponse(path)
# Static frontend served from /
app.mount("/", StaticFiles(directory="/app/static", html=True), name="static")