Commit 3d5974ad authored by Kantz's avatar Kantz
Browse files

api eingerichetet und Mathpix verbunde

parent 104161ac
.venv/
.env
__pycache__/
drawings/
\ No newline at end of file
from __future__ import annotations from __future__ import annotations
from typing import List, Optional from typing import List, Optional
import base64
import os
import time
from pathlib import Path
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from dotenv import load_dotenv
try:
from mpxpy.mathpix_client import MathpixClient
except ImportError: # pragma: no cover - optional dependency
MathpixClient = None
app = FastAPI(title="Math Tutor API", version="0.1.0") app = FastAPI(title="Math Tutor API", version="0.1.0")
load_dotenv()
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:5173"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
MATHPIX_APP_ID = os.getenv("MATHPIX_APP_ID")
MATHPIX_APP_KEY = os.getenv("MATHPIX_APP_KEY")
mathpix_client = None
if MathpixClient and MATHPIX_APP_ID and MATHPIX_APP_KEY:
mathpix_client = MathpixClient(app_id=MATHPIX_APP_ID, app_key=MATHPIX_APP_KEY)
class ChatMessage(BaseModel): class ChatMessage(BaseModel):
role: str = Field(..., pattern="^(user|assistant)$") role: str = Field(..., pattern="^(user|assistant)$")
...@@ -54,8 +82,46 @@ def save_canvas(request: CanvasSaveRequest) -> CanvasSaveResponse: ...@@ -54,8 +82,46 @@ def save_canvas(request: CanvasSaveRequest) -> CanvasSaveResponse:
if not request.data_url.startswith("data:image"): if not request.data_url.startswith("data:image"):
raise HTTPException(status_code=400, detail="data_url must be an image") raise HTTPException(status_code=400, detail="data_url must be an image")
header, encoded = request.data_url.split(",", 1)
if "image/png" not in header:
raise HTTPException(status_code=400, detail="only image/png supported")
try:
raw = base64.b64decode(encoded)
except base64.binascii.Error as exc:
raise HTTPException(status_code=400, detail="invalid base64 payload") from exc
drawings_dir = Path(__file__).resolve().parent / "storage" / "drawings"
drawings_dir.mkdir(parents=True, exist_ok=True)
timestamp = int(time.time())
safe_hint = request.filename_hint or "drawing"
safe_hint = "".join(ch for ch in safe_hint if ch.isalnum() or ch in ("-", "_"))
if not safe_hint:
safe_hint = "drawing"
filename = f"{safe_hint}-{timestamp}.png"
file_path = drawings_dir / filename
with file_path.open("wb") as handle:
handle.write(raw)
latex = "\\frac{a}{b}"
if mathpix_client:
try:
image = mathpix_client.image_new(str(file_path))
mmd = image.mmd()
conversion = mathpix_client.conversion_new(
mmd=mmd,
convert_to_md=True,
)
conversion.wait_until_complete()
latex = conversion.to_md_text()
except Exception as exc:
raise HTTPException(status_code=500, detail="mathpix failed") from exc
return CanvasSaveResponse( return CanvasSaveResponse(
status="ok", status="ok",
latex="\\frac{a}{b}", latex=latex,
saved_as=request.filename_hint, saved_as=str(file_path),
) )
fastapi
uvicorn
python-dotenv
mpxpy
pillow
\ No newline at end of file
...@@ -5,7 +5,7 @@ type CanvasDrawerProps = { ...@@ -5,7 +5,7 @@ type CanvasDrawerProps = {
isVisible: boolean; isVisible: boolean;
onToggle: () => void; onToggle: () => void;
onClear?: () => void; onClear?: () => void;
onSave?: (dataUrl: string) => void; onSave?: (dataUrl: string) => void | Promise<void>;
}; };
export default function CanvasDrawer({ export default function CanvasDrawer({
......
...@@ -63,19 +63,44 @@ export default function App() { ...@@ -63,19 +63,44 @@ export default function App() {
setIsCanvasVisible((prev) => !prev); setIsCanvasVisible((prev) => !prev);
}; };
const handleCanvasSave = (dataUrl: string) => { const handleCanvasSave = async (dataUrl: string) => {
setMessages((prev) => [
...prev,
{
id: `m-${Date.now()}-assistant`,
role: "assistant",
text: "Saving drawing and converting to LaTeX...",
},
]);
try {
const response = await fetch("http://localhost:8000/api/canvas/save", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ data_url: dataUrl, filename_hint: "canvas" }),
});
if (!response.ok) {
throw new Error(`Save failed: ${response.status}`);
}
const payload: { latex?: string } = await response.json();
if (payload.latex) {
setDraft((prev) => setDraft((prev) =>
prev ? `${prev} [drawing saved]` : "[drawing saved]" prev ? `${prev} ${payload.latex}` : payload.latex
); );
}
} catch (error) {
setMessages((prev) => [ setMessages((prev) => [
...prev, ...prev,
{ {
id: `m-${Date.now()}-assistant`, id: `m-${Date.now()}-assistant`,
role: "assistant", role: "assistant",
text: "Drawing saved (mock). Ready to send to Mathpix.", text: "Canvas save failed. Check the API logs.",
}, },
]); ]);
void dataUrl; void error;
}
}; };
const handleCiteDoc = (docId: string) => { const handleCiteDoc = (docId: string) => {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment