File size: 4,157 Bytes
c97ad08
 
 
e34beb2
 
 
 
412a862
 
 
 
e34beb2
 
 
 
 
 
 
 
 
c97ad08
 
 
 
 
 
 
412a862
c97ad08
e34beb2
0a032c8
e34beb2
12d2e34
 
c97ad08
 
 
 
 
 
412a862
 
c97ad08
412a862
c97ad08
 
412a862
c97ad08
 
 
 
0a032c8
 
c97ad08
 
 
 
12d2e34
0a032c8
 
 
 
e34beb2
 
 
 
1bbff15
016026a
e34beb2
 
 
 
016026a
e34beb2
 
 
 
 
 
 
 
 
 
 
 
12d2e34
 
ca2bb8e
0e8e243
 
 
 
 
 
 
ca2bb8e
12d2e34
 
 
 
 
e34beb2
12d2e34
 
c97ad08
 
 
 
 
 
 
 
 
 
 
 
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
"""
FitCheck — what AI can your computer actually run?

Four bricks behind a `gr.Server` (which IS a FastAPI app) serving the
hand-built frontend in static/:

  - /api/advise   : the honest verdict. Deterministic engine (engine/) over
                    catalogue.json — 110 models (real GGUF file sizes where
                    available, else parameter-count estimates), licenses, and
                    links, generated from the Hugging Face API at build time.
                    The running app makes no network calls here.
  - /api/minspecs : the reverse question — "what machine do I need for X?"
                    Same engine, inverted over a hardware ladder. Offline.
  - /api/lookup   : OPTIONAL live check of any pasted HF repo id. Walks the
                    model-tree (finetune -> base) to a catalogue entry, or does
                    labelled raw math. The one endpoint that touches the
                    network, and the UI says so.
  - /gradio_api/call/ask : the model brick (model_brick.ask) — a small local
                    LLM that explains the engine's numbers in plain words.
                    @app.api so it runs on Gradio's queue and gets ZeroGPU.
"""

from pathlib import Path

import gradio as gr
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel, Field

from engine.real_advisor import advise_real, min_specs
from engine.finetune import advise_finetune
from engine.ui_adapter import spec_from_payload
from model_brick import ask as model_ask

STATIC = Path(__file__).parent / "static"

app = gr.Server()


class AdviseIn(BaseModel):
    # Finite, non-negative, plausible bounds so absurd/negative inputs can't
    # produce nonsensical recommendations (FastAPI returns 422 outside these).
    computer: str = "Windows laptop"
    ram_gb: float | None = Field(default=16, ge=0, le=4096)
    provider: str = "none"
    gpu: str = ""
    vram_gb: float | None = Field(default=None, ge=0, le=1024)
    paste: str = ""
    usecase: str = "chat"
    custom: str = ""
    priority: str = "balanced"
    focus: str = ""   # a specific model the user clicked — show ITS breakdown
    mode: str = "run"  # "run" = inference advice; "finetune" = training advice


@app.post("/api/advise")
def api_advise(payload: AdviseIn):
    p = payload.model_dump()
    spec = spec_from_payload(p)
    if p.get("mode") == "finetune":
        return advise_finetune(p, spec)
    return advise_real(p, spec)


class MinSpecsIn(BaseModel):
    usecase: str = "chat"
    usecases: list[str] | None = None   # multi-goal: union of requirements
    mode: str = "run"                    # "run" = inference, "finetune" = training


@app.post("/api/minspecs")
def api_minspecs(payload: MinSpecsIn):
    return min_specs(payload.usecases or [payload.usecase], payload.mode)


class LookupIn(AdviseIn):
    repo: str = ""


@app.post("/api/lookup")
def api_lookup(payload: LookupIn):
    """Live lookup of one HF repo id (labelled online in the UI)."""
    from engine.hub_lookup import lookup
    p = payload.model_dump()
    return lookup(p.get("repo", ""), p, spec_from_payload(p))


@app.api(name="parse", concurrency_limit=1)
def api_parse(text: str = "") -> dict:
    """Messy machine description -> form fields, via the fine-tuned spec
    parser (cn0303/fitcheck-spec-parser). ZeroGPU via the Gradio queue."""
    from spec_brick import parse_specs
    return parse_specs(text)


@app.api(name="ask", concurrency_limit=1)
def api_ask(question: str, facts: str = "") -> dict:
    """Plain-English follow-up, grounded in the facts /api/advise returned.

    Exposed at /gradio_api/call/ask (NOT a plain POST) so it runs through
    Gradio's queue and gets a ZeroGPU allocation. `facts` is the JSON string of
    the last /api/advise result. Returns {headline, why, next_step} or {error}.
    """
    return model_ask(question, facts)


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


@app.get("/")
def index():
    return FileResponse(STATIC / "index.html")


if __name__ == "__main__":
    app.launch(server_name="0.0.0.0", server_port=7860)