Spaces:
Build error
Build error
File size: 4,707 Bytes
ca552c5 6df9ff0 2238e7a e35e5e3 830b470 6df9ff0 2238e7a ca552c5 b813321 ca552c5 b813321 e35e5e3 2238e7a e35e5e3 f179148 e35e5e3 2238e7a 2f317f9 b813321 6df9ff0 b813321 ca552c5 6df9ff0 b813321 ca552c5 2f317f9 ca552c5 6df9ff0 e35e5e3 ca552c5 2238e7a 932022e ca552c5 932022e ca552c5 2238e7a ca552c5 932022e ca552c5 2238e7a e35e5e3 ca552c5 2238e7a e35e5e3 2238e7a 6df9ff0 ca552c5 b813321 e35e5e3 b813321 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 | import asyncio
from typing import Optional
import httpx
from fastapi import FastAPI, HTTPException, UploadFile, File, Header, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from config import (
OLLAMA_BASE_URL,
DEFAULT_MODEL,
AVAILABLE_MODELS,
ALLOWED_ORIGINS,
API_KEY,
MAX_UPLOAD_BYTES,
)
from schemas import TranscriptRequest, YouTubeRequest
from ollama import stream_summary
from youtube import extract_video_id, fetch_transcript
app = FastAPI(
title="Précis API",
description="Content summarisation service powered by Ollama",
version="0.4.0",
)
app.add_middleware(
CORSMiddleware,
allow_origins=ALLOWED_ORIGINS,
allow_credentials=False,
allow_methods=["POST", "GET", "OPTIONS"],
allow_headers=["Content-Type", "X-API-Key"],
)
# Only mount frontend in production when dist/ exists
import os
if os.path.isdir("frontend/dist"):
app.mount("/", StaticFiles(directory="frontend/dist", html=True), name="static")
def verify_api_key(x_api_key: Optional[str] = Header(default=None, alias="X-API-Key")):
if not API_KEY:
raise HTTPException(
status_code=500,
detail="Server misconfigured: PRECIS_API_KEY must be set.",
)
if x_api_key != API_KEY:
raise HTTPException(status_code=401, detail="Invalid API key.")
@app.get("/")
async def root():
return {
"service": "Précis API",
"docs": "/docs",
"health": "/health",
"status": "/status",
}
@app.get("/health")
async def health():
return {"status": "healthy", "service": "precis"}
@app.get("/status")
async def status():
ollama_ok = False
try:
async with httpx.AsyncClient(timeout=5.0) as client:
r = await client.get(f"{OLLAMA_BASE_URL}/api/tags")
ollama_ok = r.status_code == 200
except Exception:
pass
return {
"service": "Précis API",
"version": "0.4.0",
"default_model": DEFAULT_MODEL,
"available_models": AVAILABLE_MODELS,
"ollama_reachable": ollama_ok,
}
@app.get("/models")
async def list_models():
try:
async with httpx.AsyncClient(timeout=5.0) as client:
r = await client.get(f"{OLLAMA_BASE_URL}/api/tags")
r.raise_for_status()
payload = r.json() if r.content else {}
installed = [m.get("name") for m in payload.get("models", []) if m.get("name")]
if installed:
default = DEFAULT_MODEL if DEFAULT_MODEL in installed else installed[0]
return {"default": default, "available": installed}
except Exception:
pass
return {"default": DEFAULT_MODEL, "available": AVAILABLE_MODELS}
@app.post("/summarize/transcript")
async def summarize_transcript(
request: TranscriptRequest,
x_api_key: Optional[str] = Header(default=None, alias="X-API-Key"),
):
verify_api_key(x_api_key)
if not request.text.strip():
raise HTTPException(status_code=400, detail="Text must not be empty.")
return stream_summary(request.text, title=request.title, model=request.model)
@app.post("/summarize/youtube")
async def summarize_youtube(
request: YouTubeRequest,
x_api_key: Optional[str] = Header(default=None, alias="X-API-Key"),
):
verify_api_key(x_api_key)
video_id = extract_video_id(request.url)
text = await asyncio.to_thread(fetch_transcript, video_id)
return stream_summary(text, model=request.model)
@app.post("/summarize/file")
async def summarize_file(
req: Request,
file: UploadFile = File(...),
model: Optional[str] = None,
x_api_key: Optional[str] = Header(default=None, alias="X-API-Key"),
):
verify_api_key(x_api_key)
content_length = req.headers.get("content-length")
if content_length and int(content_length) > MAX_UPLOAD_BYTES:
raise HTTPException(status_code=413, detail="Uploaded file is too large.")
if not file.filename.endswith(".txt"):
raise HTTPException(status_code=400, detail="Only .txt files are supported.")
content = await file.read()
if len(content) > MAX_UPLOAD_BYTES:
raise HTTPException(status_code=413, detail="Uploaded file is too large.")
try:
text = content.decode("utf-8")
except UnicodeDecodeError:
raise HTTPException(status_code=400, detail="File must be valid UTF-8 text.")
if not text.strip():
raise HTTPException(status_code=400, detail="Uploaded file is empty.")
return stream_summary(text, title=file.filename, model=model)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
|