| import type { AdminModelsSnapshot, WorldSnapshot } from "./types"; |
|
|
| const API_BASE = import.meta.env.VITE_WORLD_SIMULATOR_API ?? defaultApiBase(); |
| const ADMIN_TOKEN_STORAGE_KEY = "world-simulator-admin-token"; |
|
|
| function defaultApiBase(): string { |
| if (window.location.hostname === "127.0.0.1" && window.location.port === "5173") { |
| return "http://127.0.0.1:8000"; |
| } |
| return window.location.origin; |
| } |
|
|
| export class ApiResponseError extends Error { |
| readonly status: number; |
|
|
| constructor(message: string, status: number) { |
| super(message); |
| this.name = "ApiResponseError"; |
| this.status = status; |
| } |
| } |
|
|
| export async function fetchWorldSnapshot( |
| signal?: AbortSignal, |
| options: { warmup?: boolean } = {}, |
| ): Promise<WorldSnapshot> { |
| const url = new URL(`${API_BASE}/scene/state`, window.location.origin); |
| if (options.warmup) { |
| url.searchParams.set("warmup", "1"); |
| } |
|
|
| const response = await fetch(url, { signal }); |
| if (!response.ok) { |
| throw new ApiResponseError(`World snapshot failed: ${response.status}`, response.status); |
| } |
| return (await response.json()) as WorldSnapshot; |
| } |
|
|
| export async function tickWorld(): Promise<void> { |
| const response = await fetch(`${API_BASE}/tick`, { method: "POST" }); |
| if (!response.ok) { |
| throw new ApiResponseError(`World tick failed: ${response.status}`, response.status); |
| } |
| } |
|
|
| export async function fetchAdminModels(signal?: AbortSignal): Promise<AdminModelsSnapshot> { |
| const response = await fetch(`${API_BASE}/admin/models`, { |
| headers: adminHeaders(), |
| signal, |
| }); |
| if (!response.ok) { |
| throw new ApiResponseError(`Model profiles failed: ${response.status}`, response.status); |
| } |
| return (await response.json()) as AdminModelsSnapshot; |
| } |
|
|
| export async function setNpcModelProfile(npcId: string, profileId: string): Promise<void> { |
| const response = await fetch(`${API_BASE}/admin/npcs/${encodeURIComponent(npcId)}/model`, { |
| method: "POST", |
| headers: { |
| "Content-Type": "application/json", |
| ...adminHeaders(), |
| }, |
| body: JSON.stringify({ profile_id: profileId }), |
| }); |
| if (!response.ok) { |
| throw new ApiResponseError(`NPC model switch failed: ${response.status}`, response.status); |
| } |
| } |
|
|
| function adminHeaders(): Record<string, string> { |
| return { |
| "X-Admin-Token": adminToken(), |
| }; |
| } |
|
|
| function adminToken(): string { |
| return ( |
| import.meta.env.VITE_WORLD_SIMULATOR_ADMIN_TOKEN || |
| window.localStorage.getItem(ADMIN_TOKEN_STORAGE_KEY) || |
| "dev-admin-token" |
| ); |
| } |
|
|