testspace / frontend /src /lib /api.ts
Mayug Maniparambil
Add admin leaderboard, LLM comparison, random puzzles, clearer rules
c52f47a
Raw
History Blame Contribute Delete
3.76 kB
import { adminFetch } from "./admin";
export type Difficulty = "easy" | "medium" | "hard";
export type PuzzleType =
| "bridges"
| "flow_free"
| "galaxies"
| "loopy"
| "pattern"
| "undead";
export type SessionResponse = {
session_id: string;
engine: string;
puzzle_type: PuzzleType;
difficulty: Difficulty;
puzzle_id: string;
args: string;
status: string;
started_at: string | null;
payload: {
problem_ascii: string;
current_board_ascii: string;
image_base64: string | null;
};
};
export type PuzzleOption = {
puzzle_id: string;
title: string;
sequence: number;
puzzle_type: PuzzleType;
difficulty: Difficulty;
args: string;
};
export type SubmitResponse = {
solved: boolean;
elapsed_ms: number | null;
status: string;
verification: Record<string, unknown>;
};
export type LLMResults = {
puzzle_id: string;
models_solved: string[];
models_failed: string[];
};
export type LeaderboardEntry = {
player_name: string;
elapsed_ms: number | null;
submission_count: number;
solved: boolean;
started_at: string | null;
submitted_at: string | null;
};
export type Leaderboard = {
puzzle_id: string;
entries: LeaderboardEntry[];
};
async function handle<T>(response: Response): Promise<T> {
if (!response.ok) {
let message = response.statusText;
try {
const payload = (await response.json()) as { detail?: string };
message = payload.detail ?? message;
} catch {
// Fall back to the HTTP status text.
}
throw new Error(message);
}
return (await response.json()) as T;
}
export async function createSession(input: {
player_name: string;
puzzle_type: PuzzleType;
difficulty: Difficulty;
puzzle_id?: string;
}): Promise<SessionResponse> {
return handle<SessionResponse>(
await fetch("/api/sessions", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(input),
}),
);
}
export async function fetchPuzzleOptions(
puzzleType: PuzzleType,
difficulty: Difficulty,
): Promise<PuzzleOption[]> {
const params = new URLSearchParams({
puzzle_type: puzzleType,
difficulty,
});
return handle<PuzzleOption[]>(await fetch(`/api/puzzles?${params.toString()}`));
}
export async function fetchSession(sessionId: string): Promise<SessionResponse> {
return handle<SessionResponse>(await fetch(`/api/sessions/${sessionId}`));
}
export async function readySession(
sessionId: string,
): Promise<{ status: string; started_at: string | null }> {
return handle<{ status: string; started_at: string | null }>(
await fetch(`/api/sessions/${sessionId}/ready`, { method: "POST" }),
);
}
export async function fetchLeaderboard(
puzzleId: string,
options: { includeTest?: boolean } = {},
): Promise<Leaderboard> {
const params = new URLSearchParams();
if (options.includeTest) {
params.set("include_test", "true");
}
const qs = params.toString() ? `?${params.toString()}` : "";
const response = await adminFetch(
`/api/admin/leaderboard/${encodeURIComponent(puzzleId)}${qs}`,
);
return handle<Leaderboard>(response);
}
export async function fetchLLMResults(puzzleId: string): Promise<LLMResults | null> {
const response = await fetch(`/api/llm-results/${encodeURIComponent(puzzleId)}`);
if (response.status === 404) {
return null;
}
return handle<LLMResults>(response);
}
export async function submitSession(
sessionId: string,
boardAscii: string,
): Promise<SubmitResponse> {
return handle<SubmitResponse>(
await fetch(`/api/sessions/${sessionId}/submit`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ board_ascii: boardAscii }),
}),
);
}