File size: 2,733 Bytes
910dadd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""contimp-app: FastAPI service exposing the task registry + static frontend."""

from fastapi import FastAPI, HTTPException, Request
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel

from app import config, engine
from app.tasks import REGISTRY

app = FastAPI(title="contimp-app", version=config.APP_VERSION)


@app.middleware("http")
async def passcode_gate(request: Request, call_next):
    needs_gate = (
        config.APP_PASSCODE
        and request.url.path.startswith("/api/")
        and request.url.path != "/api/health"
    )
    if needs_gate and request.headers.get("x-contimp-passcode") != config.APP_PASSCODE:
        from fastapi.responses import JSONResponse

        return JSONResponse({"detail": "bad or missing passcode"}, status_code=401)
    return await call_next(request)


def get_task(task_id: str):
    task = REGISTRY.get(task_id)
    if task is None:
        raise HTTPException(404, f"unknown task {task_id!r}")
    return task


@app.get("/api/health")
def health() -> dict:
    return {"ok": True, "version": config.APP_VERSION, "model": config.LLM_MODEL}


@app.get("/api/tasks")
def list_tasks() -> list[dict]:
    return [task.meta() for task in REGISTRY.values()]


@app.post("/api/tasks/{task_id}/sample")
def sample(task_id: str) -> dict:
    s = get_task(task_id).sample()
    return {"input_id": s.input_id, "text": s.text}


class RunRequest(BaseModel):
    text: str
    input_id: str | None = None
    user: str = "anonymous"
    session_id: str = "unknown"
    source: str = "human"


@app.post("/api/tasks/{task_id}/run")
def run(task_id: str, body: RunRequest) -> dict:
    task = get_task(task_id)
    if not body.text.strip():
        raise HTTPException(422, "empty input")
    return engine.run_task(
        task,
        text=body.text,
        input_id=body.input_id,
        user_id=body.user,
        session_id=body.session_id,
        source="synthetic" if body.source == "synthetic" else "human",
    )


class FeedbackRequest(BaseModel):
    trace_id: str
    value: int | None = None  # 1 = up, 0 = down; None = note-only
    comment: str | None = None


@app.post("/api/feedback")
def feedback(body: FeedbackRequest) -> dict:
    if body.value is None and not (body.comment or "").strip():
        raise HTTPException(422, "feedback needs a value or a comment")
    value = None if body.value is None else (1 if body.value else 0)
    engine.record_feedback(body.trace_id, value, body.comment)
    return {"ok": True}


@app.get("/")
def index() -> FileResponse:
    return FileResponse(config.STATIC_DIR / "index.html")


app.mount("/static", StaticFiles(directory=config.STATIC_DIR), name="static")