Add OpenEnv API endpoints and fix reset/step/state support for Phase 1 checks
Browse files- Dockerfile +1 -1
- openenv.yaml +3 -3
- server/app.py +45 -6
- ui/.env.example +1 -1
- ui/src/App.jsx +31 -31
Dockerfile
CHANGED
|
@@ -16,7 +16,7 @@ COPY ui/ ./
|
|
| 16 |
# HF Spaces forwards the matching secrets as Docker build-args automatically
|
| 17 |
# when you set them in Space Settings β Variables and secrets.
|
| 18 |
ARG VITE_HF_TOKEN=""
|
| 19 |
-
ARG VITE_HF_MODEL="
|
| 20 |
ENV VITE_HF_TOKEN=${VITE_HF_TOKEN}
|
| 21 |
ENV VITE_HF_MODEL=${VITE_HF_MODEL}
|
| 22 |
|
|
|
|
| 16 |
# HF Spaces forwards the matching secrets as Docker build-args automatically
|
| 17 |
# when you set them in Space Settings β Variables and secrets.
|
| 18 |
ARG VITE_HF_TOKEN=""
|
| 19 |
+
ARG VITE_HF_MODEL="meta-llama/Llama-3.1-8B-Instruct"
|
| 20 |
ENV VITE_HF_TOKEN=${VITE_HF_TOKEN}
|
| 21 |
ENV VITE_HF_MODEL=${VITE_HF_MODEL}
|
| 22 |
|
openenv.yaml
CHANGED
|
@@ -26,11 +26,11 @@ env:
|
|
| 26 |
|
| 27 |
variables:
|
| 28 |
- name: HF_MODEL
|
| 29 |
-
default: "
|
| 30 |
description: >
|
| 31 |
Any HF chat/instruct model compatible with the [INST] prompt format.
|
| 32 |
-
|
| 33 |
-
|
| 34 |
- name: PORT
|
| 35 |
default: "7860"
|
| 36 |
description: Port exposed by the Docker container (HF Spaces default).
|
|
|
|
| 26 |
|
| 27 |
variables:
|
| 28 |
- name: HF_MODEL
|
| 29 |
+
default: "meta-llama/Llama-3.1-8B-Instruct"
|
| 30 |
description: >
|
| 31 |
Any HF chat/instruct model compatible with the [INST] prompt format.
|
| 32 |
+
If you prefer a different model, use HuggingFaceH4/blackbird-7b or
|
| 33 |
+
meta-llama/Llama-3.1-8B-Instruct (the latter may require license acceptance).
|
| 34 |
- name: PORT
|
| 35 |
default: "7860"
|
| 36 |
description: Port exposed by the Docker container (HF Spaces default).
|
server/app.py
CHANGED
|
@@ -22,6 +22,7 @@ from fastapi.middleware.cors import CORSMiddleware
|
|
| 22 |
from fastapi.responses import JSONResponse
|
| 23 |
|
| 24 |
from server.environment import GameState, InferResponse, build_prompt
|
|
|
|
| 25 |
import models
|
| 26 |
|
| 27 |
# ββ Logging βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -101,6 +102,9 @@ async def root():
|
|
| 101 |
}
|
| 102 |
|
| 103 |
|
|
|
|
|
|
|
|
|
|
| 104 |
@app.get("/api/config")
|
| 105 |
async def config():
|
| 106 |
"""Public runtime configuration for the frontend."""
|
|
@@ -111,11 +115,39 @@ async def config():
|
|
| 111 |
}
|
| 112 |
|
| 113 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 114 |
@app.post("/api/infer", response_model=InferResponse)
|
| 115 |
-
async def infer(
|
| 116 |
"""
|
| 117 |
-
Accept a
|
| 118 |
-
|
| 119 |
|
| 120 |
Inference priority: local pipeline β HF Inference API β rule fallback.
|
| 121 |
"""
|
|
@@ -126,10 +158,17 @@ async def infer(state: GameState, request: Request):
|
|
| 126 |
detail="Too many requests β wait a few seconds.",
|
| 127 |
)
|
| 128 |
|
| 129 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 130 |
logger.info(
|
| 131 |
-
f"[infer]
|
| 132 |
-
f"challenge={state.activeChallenge.type if state.activeChallenge else 'none'}"
|
| 133 |
)
|
| 134 |
|
| 135 |
# Try inference paths
|
|
|
|
| 22 |
from fastapi.responses import JSONResponse
|
| 23 |
|
| 24 |
from server.environment import GameState, InferResponse, build_prompt
|
| 25 |
+
from inference import SurvivalIslandEnvironment
|
| 26 |
import models
|
| 27 |
|
| 28 |
# ββ Logging βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 102 |
}
|
| 103 |
|
| 104 |
|
| 105 |
+
env = SurvivalIslandEnvironment()
|
| 106 |
+
|
| 107 |
+
|
| 108 |
@app.get("/api/config")
|
| 109 |
async def config():
|
| 110 |
"""Public runtime configuration for the frontend."""
|
|
|
|
| 115 |
}
|
| 116 |
|
| 117 |
|
| 118 |
+
@app.post("/openenv/reset")
|
| 119 |
+
async def openenv_reset():
|
| 120 |
+
"""Reset the OpenEnv environment and return the initial state."""
|
| 121 |
+
state = env.reset()
|
| 122 |
+
return {"state": state}
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
@app.post("/openenv/step")
|
| 126 |
+
async def openenv_step(payload: dict):
|
| 127 |
+
"""Advance the OpenEnv environment with a given action."""
|
| 128 |
+
action = payload.get("action")
|
| 129 |
+
if not action or not isinstance(action, str):
|
| 130 |
+
raise HTTPException(status_code=400, detail="Missing or invalid action")
|
| 131 |
+
next_state, reward, done, info = env.step(action)
|
| 132 |
+
return {
|
| 133 |
+
"state": next_state,
|
| 134 |
+
"reward": reward,
|
| 135 |
+
"done": done,
|
| 136 |
+
"info": info,
|
| 137 |
+
}
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
@app.get("/openenv/state")
|
| 141 |
+
async def openenv_state():
|
| 142 |
+
"""Return the current OpenEnv environment state."""
|
| 143 |
+
return {"state": env.state()}
|
| 144 |
+
|
| 145 |
+
|
| 146 |
@app.post("/api/infer", response_model=InferResponse)
|
| 147 |
+
async def infer(request: Request):
|
| 148 |
"""
|
| 149 |
+
Accept either a raw prompt or the frontend GameState JSON body.
|
| 150 |
+
Build the LLM prompt server-side and return the chosen action + thought.
|
| 151 |
|
| 152 |
Inference priority: local pipeline β HF Inference API β rule fallback.
|
| 153 |
"""
|
|
|
|
| 158 |
detail="Too many requests β wait a few seconds.",
|
| 159 |
)
|
| 160 |
|
| 161 |
+
data = await request.json()
|
| 162 |
+
state = None
|
| 163 |
+
if isinstance(data, dict) and "prompt" in data:
|
| 164 |
+
prompt = data["prompt"]
|
| 165 |
+
else:
|
| 166 |
+
state = GameState.model_validate(data)
|
| 167 |
+
prompt = build_prompt(state)
|
| 168 |
+
|
| 169 |
logger.info(
|
| 170 |
+
f"[infer] model={os.getenv('HF_MODEL')} ip={ip} "
|
| 171 |
+
f"challenge={state.activeChallenge.type if state and state.activeChallenge else 'none'}"
|
| 172 |
)
|
| 173 |
|
| 174 |
# Try inference paths
|
ui/.env.example
CHANGED
|
@@ -25,4 +25,4 @@ VITE_HF_TOKEN=YOUR_HF_TOKEN_HERE
|
|
| 25 |
#
|
| 26 |
# Requires license acceptance on HuggingFace.co:
|
| 27 |
# meta-llama/Llama-3.1-8B-Instruct
|
| 28 |
-
VITE_HF_MODEL=
|
|
|
|
| 25 |
#
|
| 26 |
# Requires license acceptance on HuggingFace.co:
|
| 27 |
# meta-llama/Llama-3.1-8B-Instruct
|
| 28 |
+
VITE_HF_MODEL=meta-llama/Llama-3.1-8B-Instruct
|
ui/src/App.jsx
CHANGED
|
@@ -13,10 +13,8 @@ const TICK_RATE = 1000;
|
|
| 13 |
const WORLD_WIDTH = 6000;
|
| 14 |
const WORLD_HEIGHT = 3000;
|
| 15 |
|
| 16 |
-
// βββ
|
| 17 |
-
|
| 18 |
-
const HF_MODEL = import.meta.env.VITE_HF_MODEL;
|
| 19 |
-
// βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 20 |
|
| 21 |
const ZONES = {
|
| 22 |
OCEAN: { baseEndX: 400, color1: '#094b65', color2: '#20a4c0' },
|
|
@@ -243,11 +241,30 @@ export default function App() {
|
|
| 243 |
|
| 244 |
const [hudState, setHudState] = useState(null);
|
| 245 |
const [showMemoryLog, setShowMemoryLog] = useState(false);
|
|
|
|
| 246 |
const canvasRef = useRef(null);
|
| 247 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
const addLog = (message, type = 'info') => {
|
| 249 |
gameRef.current.logs = [{ id: Date.now(), message, type, time: Date.now() }];
|
| 250 |
-
};
|
| 251 |
|
| 252 |
const startGame = (mode) => {
|
| 253 |
WORLD_OBJECTS = generateWorld();
|
|
@@ -473,7 +490,7 @@ export default function App() {
|
|
| 473 |
if (s.player.speedMult < 1) s.player.speedMult = Math.min(1, s.player.speedMult + 0.01);
|
| 474 |
};
|
| 475 |
|
| 476 |
-
// βββ
|
| 477 |
const callClaudeAPI = async (s) => {
|
| 478 |
s.ai.llmThinking = true;
|
| 479 |
const isNight = s.time < 6 || s.time > 18;
|
|
@@ -512,25 +529,13 @@ Valid Actions: FORAGE, HUNT, FISH, GET_WATER, SEEK_SHELTER, BUILD_CAMP, UPGRADE_
|
|
| 512 |
Respond ONLY with a raw JSON object β no markdown, no extra text. Example: {"action":"FORAGE","thought":"Need wood and resources"} [/INST]`;
|
| 513 |
|
| 514 |
try {
|
| 515 |
-
const response = await fetch(
|
| 516 |
-
|
| 517 |
-
{
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
},
|
| 523 |
-
body: JSON.stringify({
|
| 524 |
-
inputs: prompt,
|
| 525 |
-
parameters: {
|
| 526 |
-
max_new_tokens: 80,
|
| 527 |
-
temperature: 0.7,
|
| 528 |
-
return_full_text: false,
|
| 529 |
-
stop: ['\n\n', '</s>', '[INST]'],
|
| 530 |
-
},
|
| 531 |
-
}),
|
| 532 |
-
}
|
| 533 |
-
);
|
| 534 |
|
| 535 |
if (!response.ok) {
|
| 536 |
const err = await response.json().catch(() => ({}));
|
|
@@ -1173,12 +1178,7 @@ Respond ONLY with a raw JSON object β no markdown, no extra text. Example: {"a
|
|
| 1173 |
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-blue-900/20 via-zinc-950 to-zinc-950"></div>
|
| 1174 |
<div className="max-w-3xl w-full space-y-6 z-10">
|
| 1175 |
<h1 className="text-5xl font-black tracking-tighter text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-emerald-400 border-b border-zinc-800/50 pb-6 flex items-center gap-4"><Brain className="text-blue-500" size={48}/> EVOLUTIONARY AI</h1>
|
| 1176 |
-
<p className="leading-relaxed text-zinc-400 text-lg">Initialize Subject-01 into the high-fidelity 2.5D simulation. Powered by HuggingFace AI
|
| 1177 |
-
{(!HF_TOKEN || !HF_MODEL) && (
|
| 1178 |
-
<div className="bg-red-950/60 border border-red-700 rounded-xl px-5 py-3 text-red-300 text-sm font-mono">
|
| 1179 |
-
β Missing env vars: {!HF_TOKEN ? 'VITE_HF_TOKEN ' : ''}{!HF_MODEL ? 'VITE_HF_MODEL' : ''}. AI will run in offline fallback mode.
|
| 1180 |
-
</div>
|
| 1181 |
-
)}
|
| 1182 |
<div className="grid grid-cols-2 gap-6 mt-8">
|
| 1183 |
<button onClick={() => startGame('hardcore')} className="bg-zinc-900/80 backdrop-blur border border-red-900/50 p-8 rounded-xl hover:bg-red-950/40 transition-all text-left flex flex-col gap-3 group shadow-2xl">
|
| 1184 |
<div className="flex items-center gap-3 text-red-500 font-bold text-2xl"><Skull size={28}/> HARDCORE MODE</div>
|
|
|
|
| 13 |
const WORLD_WIDTH = 6000;
|
| 14 |
const WORLD_HEIGHT = 3000;
|
| 15 |
|
| 16 |
+
// βββ Runtime backend config βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 17 |
+
// Token is kept server-side; the frontend calls /api/infer and reads /api/config.
|
|
|
|
|
|
|
| 18 |
|
| 19 |
const ZONES = {
|
| 20 |
OCEAN: { baseEndX: 400, color1: '#094b65', color2: '#20a4c0' },
|
|
|
|
| 241 |
|
| 242 |
const [hudState, setHudState] = useState(null);
|
| 243 |
const [showMemoryLog, setShowMemoryLog] = useState(false);
|
| 244 |
+
const [brainConfig, setBrainConfig] = useState({ model: 'loading...', hasToken: false, localPipeline: false });
|
| 245 |
const canvasRef = useRef(null);
|
| 246 |
|
| 247 |
+
useEffect(() => {
|
| 248 |
+
const fetchConfig = async () => {
|
| 249 |
+
try {
|
| 250 |
+
const response = await fetch('/api/config');
|
| 251 |
+
if (!response.ok) return;
|
| 252 |
+
const data = await response.json();
|
| 253 |
+
setBrainConfig({
|
| 254 |
+
model: data.model || 'unknown',
|
| 255 |
+
hasToken: Boolean(data.hasToken),
|
| 256 |
+
localPipeline: Boolean(data.localPipeline),
|
| 257 |
+
});
|
| 258 |
+
} catch (error) {
|
| 259 |
+
console.warn('Failed to load backend config:', error);
|
| 260 |
+
}
|
| 261 |
+
};
|
| 262 |
+
fetchConfig();
|
| 263 |
+
}, []);
|
| 264 |
+
|
| 265 |
const addLog = (message, type = 'info') => {
|
| 266 |
gameRef.current.logs = [{ id: Date.now(), message, type, time: Date.now() }];
|
| 267 |
+
};
|
| 268 |
|
| 269 |
const startGame = (mode) => {
|
| 270 |
WORLD_OBJECTS = generateWorld();
|
|
|
|
| 490 |
if (s.player.speedMult < 1) s.player.speedMult = Math.min(1, s.player.speedMult + 0.01);
|
| 491 |
};
|
| 492 |
|
| 493 |
+
// βββ BACKEND INFERENCE CALL (via /api/infer) βββββββββββββββββββββββββββββ
|
| 494 |
const callClaudeAPI = async (s) => {
|
| 495 |
s.ai.llmThinking = true;
|
| 496 |
const isNight = s.time < 6 || s.time > 18;
|
|
|
|
| 529 |
Respond ONLY with a raw JSON object β no markdown, no extra text. Example: {"action":"FORAGE","thought":"Need wood and resources"} [/INST]`;
|
| 530 |
|
| 531 |
try {
|
| 532 |
+
const response = await fetch('/api/infer', {
|
| 533 |
+
method: 'POST',
|
| 534 |
+
headers: {
|
| 535 |
+
'Content-Type': 'application/json',
|
| 536 |
+
},
|
| 537 |
+
body: JSON.stringify({ prompt }),
|
| 538 |
+
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 539 |
|
| 540 |
if (!response.ok) {
|
| 541 |
const err = await response.json().catch(() => ({}));
|
|
|
|
| 1178 |
<div className="absolute inset-0 bg-[radial-gradient(ellipse_at_center,_var(--tw-gradient-stops))] from-blue-900/20 via-zinc-950 to-zinc-950"></div>
|
| 1179 |
<div className="max-w-3xl w-full space-y-6 z-10">
|
| 1180 |
<h1 className="text-5xl font-black tracking-tighter text-transparent bg-clip-text bg-gradient-to-r from-blue-400 to-emerald-400 border-b border-zinc-800/50 pb-6 flex items-center gap-4"><Brain className="text-blue-500" size={48}/> EVOLUTIONARY AI</h1>
|
| 1181 |
+
<p className="leading-relaxed text-zinc-400 text-lg">Initialize Subject-01 into the high-fidelity 2.5D simulation. Powered by HuggingFace AI with persistent memory across 6 generations.</p>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1182 |
<div className="grid grid-cols-2 gap-6 mt-8">
|
| 1183 |
<button onClick={() => startGame('hardcore')} className="bg-zinc-900/80 backdrop-blur border border-red-900/50 p-8 rounded-xl hover:bg-red-950/40 transition-all text-left flex flex-col gap-3 group shadow-2xl">
|
| 1184 |
<div className="flex items-center gap-3 text-red-500 font-bold text-2xl"><Skull size={28}/> HARDCORE MODE</div>
|