This commit is contained in:
2026-09-27 23:06:24 +08:00
parent 2d4f9230b1
commit 22450e5bb3
40 changed files with 3040 additions and 697 deletions
+71 -5
View File
@@ -1,11 +1,21 @@
from __future__ import annotations
from fastapi import APIRouter, Request
from pathlib import Path
from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel, Field
from app.presets import PRESETS
from app.services.i18n import DEFAULT_LOCALE, locale_from_request
router = APIRouter(prefix="/api")
router = APIRouter(prefix="/api")
CUSTOM_SIMULATION_DIR = Path(__file__).resolve().parents[3] / "custom_simulation"
class CustomPresetPayload(BaseModel):
title: str = Field(min_length=1, max_length=160)
content: str = Field(min_length=1)
def _localize(value, locale: str):
@@ -30,6 +40,62 @@ def _localize(value, locale: str):
@router.get("/presets")
async def get_presets(request: Request):
locale = locale_from_request(request)
return {"presets": _localize(PRESETS, locale)}
async def get_presets(request: Request):
locale = locale_from_request(request)
return {"presets": _localize(PRESETS, locale)}
def _custom_presets():
CUSTOM_SIMULATION_DIR.mkdir(parents=True, exist_ok=True)
presets = []
for path in sorted(CUSTOM_SIMULATION_DIR.glob("*.txt"), key=lambda p: p.name.lower()):
try:
content = path.read_text(encoding="utf-8")
except (OSError, UnicodeDecodeError):
continue
presets.append({
"id": f"custom:{path.name}",
"name": path.stem,
"rules": content,
"custom": True,
"filename": path.name,
})
return presets
@router.get("/custom-presets")
async def get_custom_presets():
"""Load user-authored scenario files from the project custom_simulation folder."""
return {"presets": _custom_presets()}
@router.post("/custom-presets")
async def create_custom_preset(payload: CustomPresetPayload):
"""Save a custom scenario, suffixing duplicate titles with _1, _2, ... ."""
title = " ".join(payload.title.strip().split())
# Keep unicode letters (including Chinese) while preventing path traversal.
title = title.replace("/", "/").replace("\\", "\")
if not title or title in {".", ".."}:
raise HTTPException(status_code=400, detail="Title is required")
CUSTOM_SIMULATION_DIR.mkdir(parents=True, exist_ok=True)
candidate = CUSTOM_SIMULATION_DIR / f"{title}.txt"
index = 1
while candidate.exists():
candidate = CUSTOM_SIMULATION_DIR / f"{title}_{index}.txt"
index += 1
try:
candidate.write_text(payload.content, encoding="utf-8")
except OSError as exc:
raise HTTPException(status_code=500, detail=f"Unable to save preset: {exc}") from exc
return {
"preset": {
"id": f"custom:{candidate.name}",
"name": candidate.stem,
"rules": payload.content,
"custom": True,
"filename": candidate.name,
}
}