Commit ·
b5cb7df
1
Parent(s): d8da231
Feature: Add Warm LLM button to pre-warm Modal inference container
Browse files- api.py +21 -0
- frontend/static/index.html +67 -1
api.py
CHANGED
|
@@ -167,3 +167,24 @@ async def run_full_game(req: StartGameRequest):
|
|
| 167 |
def get_default_prompt(grid_size: int = 20):
|
| 168 |
from backend.agent.agent import Agent
|
| 169 |
return {"default_prompt": Agent.get_default_system_prompt(grid_size=grid_size)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 167 |
def get_default_prompt(grid_size: int = 20):
|
| 168 |
from backend.agent.agent import Agent
|
| 169 |
return {"default_prompt": Agent.get_default_system_prompt(grid_size=grid_size)}
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
@app.post("/api/model/warm")
|
| 173 |
+
async def warm_model():
|
| 174 |
+
from backend.agent.llm_client import get_llm_client
|
| 175 |
+
try:
|
| 176 |
+
client = get_llm_client()
|
| 177 |
+
response = await client.chat.completions.create(
|
| 178 |
+
model="google/gemma-4-26B-A4B-it",
|
| 179 |
+
messages=[{"role": "user", "content": "ping"}],
|
| 180 |
+
max_tokens=5
|
| 181 |
+
)
|
| 182 |
+
if response and response.choices:
|
| 183 |
+
return {
|
| 184 |
+
"status": "success",
|
| 185 |
+
"message": "Model warmed up successfully!",
|
| 186 |
+
"response": response.choices[0].message.content
|
| 187 |
+
}
|
| 188 |
+
return {"status": "warning", "message": "No choices returned from model."}
|
| 189 |
+
except Exception as e:
|
| 190 |
+
return {"status": "error", "detail": str(e)}
|
frontend/static/index.html
CHANGED
|
@@ -276,6 +276,39 @@ body {
|
|
| 276 |
35%, 100% { transform: translateX(100%); }
|
| 277 |
}
|
| 278 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 279 |
/* ══════════════════════ GAME WRAPPER ══════════════════════ */
|
| 280 |
#game {
|
| 281 |
display: none; flex-direction: column; height: 100vh;
|
|
@@ -1114,7 +1147,10 @@ body {
|
|
| 1114 |
<div class="lobby-prompt-wrap">
|
| 1115 |
<textarea id="systemPromptInput" placeholder="Configure the agent system prompt..."></textarea>
|
| 1116 |
</div>
|
| 1117 |
-
<
|
|
|
|
|
|
|
|
|
|
| 1118 |
</div>
|
| 1119 |
</div>
|
| 1120 |
</div>
|
|
@@ -2863,6 +2899,36 @@ function updateUI() {
|
|
| 2863 |
|
| 2864 |
/* ═══════════ KEYBOARD ═══════════ */
|
| 2865 |
document.getElementById('startBtn').addEventListener('click', startGame);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2866 |
document.getElementById('stepBtn').addEventListener('click', step);
|
| 2867 |
document.getElementById('autoBtn').addEventListener('click', auto);
|
| 2868 |
|
|
|
|
| 276 |
35%, 100% { transform: translateX(100%); }
|
| 277 |
}
|
| 278 |
|
| 279 |
+
.lobby-warm {
|
| 280 |
+
padding: 15px; border: 1px solid var(--border); border-radius: 12px;
|
| 281 |
+
background: var(--surface2);
|
| 282 |
+
color: var(--text-dim);
|
| 283 |
+
font-family: 'Space Grotesk', sans-serif;
|
| 284 |
+
font-size: 13px; font-weight: 700;
|
| 285 |
+
text-transform: uppercase; letter-spacing: 0.15em;
|
| 286 |
+
cursor: pointer; position: relative; overflow: hidden;
|
| 287 |
+
transition: all 0.3s cubic-bezier(0.4,0,0.2,1);
|
| 288 |
+
}
|
| 289 |
+
.lobby-warm:hover {
|
| 290 |
+
border-color: var(--blue);
|
| 291 |
+
color: var(--text);
|
| 292 |
+
box-shadow: 0 0 15px rgba(74,158,255,0.25);
|
| 293 |
+
transform: translateY(-2px);
|
| 294 |
+
}
|
| 295 |
+
.lobby-warm:disabled { opacity: 0.8; cursor: not-allowed; }
|
| 296 |
+
.lobby-warm.warming {
|
| 297 |
+
border-color: var(--accent);
|
| 298 |
+
color: var(--accent);
|
| 299 |
+
box-shadow: 0 0 15px var(--accent-glow);
|
| 300 |
+
animation: warmPulse 1.0s infinite alternate;
|
| 301 |
+
}
|
| 302 |
+
.lobby-warm.warmed {
|
| 303 |
+
border-color: var(--cyan);
|
| 304 |
+
color: var(--cyan);
|
| 305 |
+
box-shadow: 0 0 15px rgba(0,229,255,0.25);
|
| 306 |
+
}
|
| 307 |
+
@keyframes warmPulse {
|
| 308 |
+
0% { opacity: 0.6; }
|
| 309 |
+
100% { opacity: 1; }
|
| 310 |
+
}
|
| 311 |
+
|
| 312 |
/* ══════════════════════ GAME WRAPPER ══════════════════════ */
|
| 313 |
#game {
|
| 314 |
display: none; flex-direction: column; height: 100vh;
|
|
|
|
| 1147 |
<div class="lobby-prompt-wrap">
|
| 1148 |
<textarea id="systemPromptInput" placeholder="Configure the agent system prompt..."></textarea>
|
| 1149 |
</div>
|
| 1150 |
+
<div class="lobby-action-group" style="display: flex; gap: 12px; margin-top: 24px;">
|
| 1151 |
+
<button class="lobby-deploy" id="startBtn" style="margin-top: 0; flex: 2;">⚔ Deploy Agents</button>
|
| 1152 |
+
<button class="lobby-warm" id="warmBtn" style="flex: 1;" title="Pre-warm serverless inference models to prevent cold start latency">⚡ Warm LLM</button>
|
| 1153 |
+
</div>
|
| 1154 |
</div>
|
| 1155 |
</div>
|
| 1156 |
</div>
|
|
|
|
| 2899 |
|
| 2900 |
/* ═══════════ KEYBOARD ═══════════ */
|
| 2901 |
document.getElementById('startBtn').addEventListener('click', startGame);
|
| 2902 |
+
|
| 2903 |
+
document.getElementById('warmBtn').addEventListener('click', async () => {
|
| 2904 |
+
const btn = document.getElementById('warmBtn');
|
| 2905 |
+
if (btn.disabled) return;
|
| 2906 |
+
|
| 2907 |
+
btn.disabled = true;
|
| 2908 |
+
btn.classList.add('warming');
|
| 2909 |
+
btn.classList.remove('warmed');
|
| 2910 |
+
btn.textContent = 'Warming...';
|
| 2911 |
+
|
| 2912 |
+
try {
|
| 2913 |
+
const res = await fetch('/api/model/warm', { method: 'POST' });
|
| 2914 |
+
const data = await res.json();
|
| 2915 |
+
if (data.status === 'success') {
|
| 2916 |
+
btn.classList.remove('warming');
|
| 2917 |
+
btn.classList.add('warmed');
|
| 2918 |
+
btn.textContent = 'Ready ✅';
|
| 2919 |
+
} else {
|
| 2920 |
+
btn.classList.remove('warming');
|
| 2921 |
+
btn.textContent = 'Error ❌';
|
| 2922 |
+
btn.disabled = false;
|
| 2923 |
+
console.error('Failed to warm model:', data.detail || data.message);
|
| 2924 |
+
}
|
| 2925 |
+
} catch (err) {
|
| 2926 |
+
btn.classList.remove('warming');
|
| 2927 |
+
btn.textContent = 'Error ❌';
|
| 2928 |
+
btn.disabled = false;
|
| 2929 |
+
console.error('Failed to warm model:', err);
|
| 2930 |
+
}
|
| 2931 |
+
});
|
| 2932 |
document.getElementById('stepBtn').addEventListener('click', step);
|
| 2933 |
document.getElementById('autoBtn').addEventListener('click', auto);
|
| 2934 |
|