Admin: Word Search theme authoring + tidy word-pool chips
* New "Word Search themes" panel in the Games tab: enter a theme name + words, with live validation (4–8 letters, alpha, deduped) and a count vs the 28 needed to fill all three sizes. An "✨ Suggest a word" button asks the LLM for one fresh word that fits the theme. Save/edit/remove; authored themes join the daily fallback rotation alongside the curated ones (wordsearch_themes table). The system still handles word distribution across sizes + placement. * Daily Word pool's added-word chips now scroll within a bounded area so the console stays tidy as the list grows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -350,6 +350,17 @@ class ClientErrorBody(BaseModel):
|
||||
version: str = ""
|
||||
|
||||
|
||||
class WordsearchThemeBody(BaseModel):
|
||||
theme: str
|
||||
words: list[str] = []
|
||||
id: int | None = None
|
||||
|
||||
|
||||
class WordsearchSuggestBody(BaseModel):
|
||||
theme: str
|
||||
existing: list[str] = []
|
||||
|
||||
|
||||
class EmailStartRequest(BaseModel):
|
||||
email: str
|
||||
|
||||
@@ -1512,6 +1523,43 @@ def create_app() -> FastAPI:
|
||||
games.remove_pool_word(conn, word)
|
||||
return games.pool_summary(conn)
|
||||
|
||||
# --- Admin: Word Search theme authoring ---
|
||||
@app.get("/api/admin/wordsearch/themes")
|
||||
def admin_ws_themes(request: Request) -> list[dict]:
|
||||
with get_conn() as conn:
|
||||
_require_admin(conn, request)
|
||||
return games.list_wordsearch_themes(conn)
|
||||
|
||||
@app.post("/api/admin/wordsearch/themes")
|
||||
def admin_ws_theme_save(body: WordsearchThemeBody, request: Request) -> dict:
|
||||
with get_conn() as conn:
|
||||
_require_admin(conn, request)
|
||||
res = games.save_wordsearch_theme(conn, body.theme, body.words, body.id)
|
||||
if "error" in res:
|
||||
raise HTTPException(status_code=400, detail=res["error"])
|
||||
return {**res, "themes": games.list_wordsearch_themes(conn)}
|
||||
|
||||
@app.delete("/api/admin/wordsearch/themes/{tid}")
|
||||
def admin_ws_theme_remove(tid: int, request: Request) -> list[dict]:
|
||||
with get_conn() as conn:
|
||||
_require_admin(conn, request)
|
||||
games.remove_wordsearch_theme(conn, tid)
|
||||
return games.list_wordsearch_themes(conn)
|
||||
|
||||
@app.post("/api/admin/wordsearch/suggest")
|
||||
def admin_ws_suggest(body: WordsearchSuggestBody, request: Request) -> dict:
|
||||
with get_conn() as conn:
|
||||
_require_admin(conn, request)
|
||||
from .llm import LocalModelClient
|
||||
try:
|
||||
client = LocalModelClient.from_env()
|
||||
except Exception: # noqa: BLE001
|
||||
client = None
|
||||
res = games.suggest_wordsearch_word(client, body.theme, body.existing)
|
||||
if "error" in res:
|
||||
raise HTTPException(status_code=503, detail=res["error"])
|
||||
return res
|
||||
|
||||
@app.get("/api/since", response_model=FeedResponse)
|
||||
def feed_since(ts: str = Query(...), prefs: str | None = Query(None)) -> FeedResponse:
|
||||
# A calm welcome-back cue: accepted/non-dup/visible articles discovered
|
||||
|
||||
Reference in New Issue
Block a user