omaryashraf commited on
Commit
eff6f73
verified
1 Parent(s): 8fdd693

Upload folder using huggingface_hub

Browse files
Files changed (2) hide show
  1. server/app.py +87 -1
  2. server/demo.html +423 -0
server/app.py CHANGED
@@ -11,7 +11,7 @@ from core.env_server import create_app
11
  from envs.warehouse_env.models import WarehouseAction, WarehouseObservation
12
  from envs.warehouse_env.server.warehouse_environment import WarehouseEnvironment
13
  from fastapi import FastAPI
14
- from fastapi.responses import JSONResponse, HTMLResponse
15
 
16
 
17
  # Get configuration from environment variables
@@ -61,6 +61,81 @@ async def render_html():
61
  status_code=500, content={"error": f"Failed to render HTML: {str(e)}"}
62
  )
63
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
 
65
  # Add health check endpoint
66
  @app.get("/health")
@@ -76,6 +151,17 @@ async def health():
76
  }
77
 
78
 
 
 
 
 
 
 
 
 
 
 
 
79
  if __name__ == "__main__":
80
  import uvicorn
81
 
 
11
  from envs.warehouse_env.models import WarehouseAction, WarehouseObservation
12
  from envs.warehouse_env.server.warehouse_environment import WarehouseEnvironment
13
  from fastapi import FastAPI
14
+ from fastapi.responses import JSONResponse, HTMLResponse, FileResponse
15
 
16
 
17
  # Get configuration from environment variables
 
61
  status_code=500, content={"error": f"Failed to render HTML: {str(e)}"}
62
  )
63
 
64
+ @app.post("/auto-step")
65
+ async def auto_step():
66
+ """Execute one step using a greedy agent."""
67
+ try:
68
+ # Get current observation
69
+ if warehouse_env.is_done:
70
+ return JSONResponse(content={
71
+ "done": True,
72
+ "message": "Episode finished. Reset to start a new episode."
73
+ })
74
+
75
+ # Simple greedy policy
76
+ action_id = _get_greedy_action()
77
+ action = WarehouseAction(action_id=action_id)
78
+
79
+ # Execute step
80
+ result = warehouse_env.step(action)
81
+
82
+ return JSONResponse(content={
83
+ "action": action.action_name,
84
+ "message": result.message,
85
+ "reward": result.reward,
86
+ "done": result.done,
87
+ "step_count": result.step_count,
88
+ "packages_delivered": result.packages_delivered,
89
+ "robot_position": result.robot_position,
90
+ })
91
+ except Exception as e:
92
+ return JSONResponse(
93
+ status_code=500, content={"error": f"Failed to execute auto-step: {str(e)}"}
94
+ )
95
+
96
+ def _get_greedy_action() -> int:
97
+ """Simple greedy policy: move toward nearest target."""
98
+ robot_x, robot_y = warehouse_env.robot_position
99
+
100
+ # Determine target location
101
+ if warehouse_env.robot_carrying is None:
102
+ # Not carrying: move toward nearest waiting package
103
+ target = None
104
+ min_dist = float('inf')
105
+
106
+ for package in warehouse_env.packages:
107
+ if package.status == "waiting":
108
+ px, py = package.pickup_location
109
+ dist = abs(robot_x - px) + abs(robot_y - py)
110
+ if dist < min_dist:
111
+ min_dist = dist
112
+ target = (px, py)
113
+
114
+ if target is None:
115
+ return 4 # Try to pick up if at location
116
+
117
+ target_x, target_y = target
118
+ else:
119
+ # Carrying: move toward dropoff zone
120
+ package = next((p for p in warehouse_env.packages if p.id == warehouse_env.robot_carrying), None)
121
+ if package:
122
+ target_x, target_y = package.dropoff_location
123
+ else:
124
+ return 4 # Try action
125
+
126
+ # Simple pathfinding: move closer on one axis at a time
127
+ if robot_x < target_x:
128
+ return 3 # RIGHT
129
+ elif robot_x > target_x:
130
+ return 2 # LEFT
131
+ elif robot_y < target_y:
132
+ return 1 # DOWN
133
+ elif robot_y > target_y:
134
+ return 0 # UP
135
+ else:
136
+ # At target location
137
+ return 4 if warehouse_env.robot_carrying is None else 5
138
+
139
 
140
  # Add health check endpoint
141
  @app.get("/health")
 
151
  }
152
 
153
 
154
+ @app.get("/demo")
155
+ async def demo():
156
+ """Serve the interactive demo page."""
157
+ import pathlib
158
+ demo_path = pathlib.Path(__file__).parent / "demo.html"
159
+ if demo_path.exists():
160
+ return FileResponse(demo_path)
161
+ else:
162
+ return HTMLResponse(content="<h1>Demo page not found</h1><p>Please check the server configuration.</p>")
163
+
164
+
165
  if __name__ == "__main__":
166
  import uvicorn
167
 
server/demo.html ADDED
@@ -0,0 +1,423 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Warehouse Optimization Demo</title>
7
+ <style>
8
+ body {
9
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
10
+ max-width: 1200px;
11
+ margin: 0 auto;
12
+ padding: 20px;
13
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
14
+ min-height: 100vh;
15
+ }
16
+ .container {
17
+ background: white;
18
+ border-radius: 15px;
19
+ padding: 30px;
20
+ box-shadow: 0 10px 40px rgba(0,0,0,0.3);
21
+ }
22
+ h1 {
23
+ color: #333;
24
+ text-align: center;
25
+ margin-bottom: 10px;
26
+ }
27
+ .subtitle {
28
+ text-align: center;
29
+ color: #666;
30
+ margin-bottom: 30px;
31
+ }
32
+ .controls {
33
+ display: flex;
34
+ gap: 15px;
35
+ margin-bottom: 20px;
36
+ flex-wrap: wrap;
37
+ justify-content: center;
38
+ }
39
+ button {
40
+ padding: 12px 24px;
41
+ font-size: 16px;
42
+ border: none;
43
+ border-radius: 8px;
44
+ cursor: pointer;
45
+ transition: all 0.3s;
46
+ font-weight: 600;
47
+ }
48
+ .btn-primary {
49
+ background: #4CAF50;
50
+ color: white;
51
+ }
52
+ .btn-primary:hover {
53
+ background: #45a049;
54
+ transform: translateY(-2px);
55
+ box-shadow: 0 4px 12px rgba(76, 175, 80, 0.4);
56
+ }
57
+ .btn-secondary {
58
+ background: #2196F3;
59
+ color: white;
60
+ }
61
+ .btn-secondary:hover {
62
+ background: #0b7dda;
63
+ transform: translateY(-2px);
64
+ box-shadow: 0 4px 12px rgba(33, 150, 243, 0.4);
65
+ }
66
+ .btn-danger {
67
+ background: #f44336;
68
+ color: white;
69
+ }
70
+ .btn-danger:hover {
71
+ background: #da190b;
72
+ transform: translateY(-2px);
73
+ box-shadow: 0 4px 12px rgba(244, 67, 54, 0.4);
74
+ }
75
+ button:disabled {
76
+ opacity: 0.5;
77
+ cursor: not-allowed;
78
+ transform: none !important;
79
+ }
80
+ .difficulty-selector {
81
+ display: flex;
82
+ gap: 10px;
83
+ align-items: center;
84
+ justify-content: center;
85
+ margin-bottom: 20px;
86
+ }
87
+ .difficulty-selector label {
88
+ font-weight: 600;
89
+ color: #333;
90
+ }
91
+ .difficulty-selector select {
92
+ padding: 8px 16px;
93
+ border-radius: 6px;
94
+ border: 2px solid #ddd;
95
+ font-size: 14px;
96
+ }
97
+ #visualization {
98
+ border: 3px solid #333;
99
+ border-radius: 10px;
100
+ padding: 20px;
101
+ margin-bottom: 20px;
102
+ min-height: 400px;
103
+ background: #fafafa;
104
+ }
105
+ .stats {
106
+ display: grid;
107
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
108
+ gap: 15px;
109
+ margin-bottom: 20px;
110
+ }
111
+ .stat-card {
112
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
113
+ color: white;
114
+ padding: 15px;
115
+ border-radius: 8px;
116
+ text-align: center;
117
+ }
118
+ .stat-card h3 {
119
+ margin: 0 0 5px 0;
120
+ font-size: 14px;
121
+ opacity: 0.9;
122
+ }
123
+ .stat-card p {
124
+ margin: 0;
125
+ font-size: 24px;
126
+ font-weight: bold;
127
+ }
128
+ .mode-toggle {
129
+ text-align: center;
130
+ margin-bottom: 20px;
131
+ padding: 15px;
132
+ background: #f0f0f0;
133
+ border-radius: 8px;
134
+ }
135
+ .manual-controls {
136
+ display: grid;
137
+ grid-template-columns: repeat(3, 80px);
138
+ gap: 10px;
139
+ justify-content: center;
140
+ margin-top: 15px;
141
+ }
142
+ .manual-controls button {
143
+ width: 80px;
144
+ height: 80px;
145
+ font-size: 24px;
146
+ }
147
+ .hidden {
148
+ display: none;
149
+ }
150
+ #message {
151
+ padding: 15px;
152
+ border-radius: 8px;
153
+ margin-bottom: 20px;
154
+ text-align: center;
155
+ font-weight: 500;
156
+ }
157
+ .success {
158
+ background: #d4edda;
159
+ color: #155724;
160
+ border: 1px solid #c3e6cb;
161
+ }
162
+ .error {
163
+ background: #f8d7da;
164
+ color: #721c24;
165
+ border: 1px solid #f5c6cb;
166
+ }
167
+ .info {
168
+ background: #d1ecf1;
169
+ color: #0c5460;
170
+ border: 1px solid #bee5eb;
171
+ }
172
+ .speed-control {
173
+ text-align: center;
174
+ margin-bottom: 15px;
175
+ }
176
+ .speed-control label {
177
+ margin-right: 10px;
178
+ font-weight: 600;
179
+ }
180
+ .speed-control input {
181
+ width: 200px;
182
+ }
183
+ </style>
184
+ </head>
185
+ <body>
186
+ <div class="container">
187
+ <h1>馃彮 Warehouse Optimization Environment</h1>
188
+ <p class="subtitle">Watch an AI agent navigate a warehouse to pick up and deliver packages!</p>
189
+
190
+ <div class="difficulty-selector">
191
+ <label for="difficulty">Difficulty Level:</label>
192
+ <select id="difficulty">
193
+ <option value="1">Level 1 - Simple (5脳5, 1 package)</option>
194
+ <option value="2" selected>Level 2 - Easy (8脳8, 2 packages)</option>
195
+ <option value="3">Level 3 - Medium (10脳10, 3 packages)</option>
196
+ <option value="4">Level 4 - Hard (15脳15, 5 packages)</option>
197
+ <option value="5">Level 5 - Expert (20脳20, 8 packages)</option>
198
+ </select>
199
+ </div>
200
+
201
+ <div class="stats">
202
+ <div class="stat-card">
203
+ <h3>Steps</h3>
204
+ <p id="steps">0 / 0</p>
205
+ </div>
206
+ <div class="stat-card">
207
+ <h3>Packages Delivered</h3>
208
+ <p id="delivered">0 / 0</p>
209
+ </div>
210
+ <div class="stat-card">
211
+ <h3>Cumulative Reward</h3>
212
+ <p id="reward">0.0</p>
213
+ </div>
214
+ </div>
215
+
216
+ <div id="message" class="hidden"></div>
217
+
218
+ <div class="mode-toggle">
219
+ <label>
220
+ <input type="radio" name="mode" value="auto" checked> Auto-Play Mode
221
+ </label>
222
+ <label style="margin-left: 20px;">
223
+ <input type="radio" name="mode" value="manual"> Manual Control
224
+ </label>
225
+ </div>
226
+
227
+ <div id="auto-controls" class="controls">
228
+ <button class="btn-primary" id="startBtn">鈻讹笍 Start Auto-Play</button>
229
+ <button class="btn-danger" id="stopBtn" disabled>鈴革笍 Stop</button>
230
+ <button class="btn-secondary" id="resetBtn">馃攧 Reset</button>
231
+ <div class="speed-control">
232
+ <label for="speed">Speed:</label>
233
+ <input type="range" id="speed" min="100" max="2000" value="500" step="100">
234
+ <span id="speedLabel">500ms</span>
235
+ </div>
236
+ </div>
237
+
238
+ <div id="manual-controls" class="hidden">
239
+ <div class="controls">
240
+ <button class="btn-secondary" id="resetManualBtn">馃攧 Reset</button>
241
+ </div>
242
+ <div class="manual-controls">
243
+ <div></div>
244
+ <button class="btn-primary" onclick="manualAction(0)">猬嗭笍</button>
245
+ <div></div>
246
+ <button class="btn-primary" onclick="manualAction(2)">猬咃笍</button>
247
+ <button class="btn-primary" onclick="manualAction(4)">馃摝 Pick</button>
248
+ <button class="btn-primary" onclick="manualAction(3)">鉃★笍</button>
249
+ <div></div>
250
+ <button class="btn-primary" onclick="manualAction(1)">猬囷笍</button>
251
+ <button class="btn-primary" onclick="manualAction(5)">馃摛 Drop</button>
252
+ </div>
253
+ </div>
254
+
255
+ <div id="visualization">
256
+ <p style="text-align: center; color: #999;">Click "Start Auto-Play" or "Reset" to begin!</p>
257
+ </div>
258
+ </div>
259
+
260
+ <script>
261
+ let autoPlayInterval = null;
262
+ let currentMode = 'auto';
263
+ const baseUrl = window.location.origin;
264
+
265
+ // Mode switching
266
+ document.querySelectorAll('input[name="mode"]').forEach(radio => {
267
+ radio.addEventListener('change', (e) => {
268
+ currentMode = e.target.value;
269
+ if (currentMode === 'auto') {
270
+ document.getElementById('auto-controls').classList.remove('hidden');
271
+ document.getElementById('manual-controls').classList.add('hidden');
272
+ } else {
273
+ document.getElementById('auto-controls').classList.add('hidden');
274
+ document.getElementById('manual-controls').classList.remove('hidden');
275
+ stopAutoPlay();
276
+ }
277
+ });
278
+ });
279
+
280
+ // Speed control
281
+ document.getElementById('speed').addEventListener('input', (e) => {
282
+ const speed = e.target.value;
283
+ document.getElementById('speedLabel').textContent = speed + 'ms';
284
+ if (autoPlayInterval) {
285
+ stopAutoPlay();
286
+ startAutoPlay();
287
+ }
288
+ });
289
+
290
+ async function reset() {
291
+ try {
292
+ showMessage('Resetting environment...', 'info');
293
+ const response = await fetch(`${baseUrl}/reset`, {
294
+ method: 'POST',
295
+ headers: { 'Content-Type': 'application/json' },
296
+ body: JSON.stringify({})
297
+ });
298
+ const data = await response.json();
299
+ updateStats(data.observation);
300
+ await refreshVisualization();
301
+ showMessage('Environment reset successfully!', 'success');
302
+ } catch (error) {
303
+ showMessage('Error resetting environment: ' + error.message, 'error');
304
+ }
305
+ }
306
+
307
+ async function startAutoPlay() {
308
+ const startBtn = document.getElementById('startBtn');
309
+ const stopBtn = document.getElementById('stopBtn');
310
+
311
+ startBtn.disabled = true;
312
+ stopBtn.disabled = false;
313
+
314
+ const speed = parseInt(document.getElementById('speed').value);
315
+
316
+ autoPlayInterval = setInterval(async () => {
317
+ try {
318
+ const response = await fetch(`${baseUrl}/auto-step`, {
319
+ method: 'POST'
320
+ });
321
+ const data = await response.json();
322
+
323
+ if (data.done) {
324
+ stopAutoPlay();
325
+ showMessage(`Episode complete! Delivered ${data.packages_delivered} packages. Final reward: ${data.reward}`, 'success');
326
+ return;
327
+ }
328
+
329
+ updateStatsFromStep(data);
330
+ await refreshVisualization();
331
+ showMessage(`Action: ${data.action} - ${data.message}`, 'info');
332
+ } catch (error) {
333
+ stopAutoPlay();
334
+ showMessage('Error during auto-play: ' + error.message, 'error');
335
+ }
336
+ }, speed);
337
+ }
338
+
339
+ function stopAutoPlay() {
340
+ if (autoPlayInterval) {
341
+ clearInterval(autoPlayInterval);
342
+ autoPlayInterval = null;
343
+ }
344
+ document.getElementById('startBtn').disabled = false;
345
+ document.getElementById('stopBtn').disabled = true;
346
+ }
347
+
348
+ async function manualAction(actionId) {
349
+ try {
350
+ const response = await fetch(`${baseUrl}/step`, {
351
+ method: 'POST',
352
+ headers: { 'Content-Type': 'application/json' },
353
+ body: JSON.stringify({ action: { action_id: actionId } })
354
+ });
355
+ const data = await response.json();
356
+ updateStats(data.observation);
357
+ await refreshVisualization();
358
+
359
+ if (data.observation.message) {
360
+ showMessage(data.observation.message, data.observation.action_success ? 'success' : 'error');
361
+ }
362
+
363
+ if (data.done) {
364
+ showMessage(`Episode complete! Delivered ${data.observation.packages_delivered} packages.`, 'success');
365
+ }
366
+ } catch (error) {
367
+ showMessage('Error: ' + error.message, 'error');
368
+ }
369
+ }
370
+
371
+ async function refreshVisualization() {
372
+ try {
373
+ const response = await fetch(`${baseUrl}/render/html`);
374
+ const html = await response.text();
375
+ document.getElementById('visualization').innerHTML = html;
376
+ } catch (error) {
377
+ console.error('Error refreshing visualization:', error);
378
+ }
379
+ }
380
+
381
+ function updateStats(obs) {
382
+ document.getElementById('steps').textContent = `${obs.step_count} / ${obs.time_remaining + obs.step_count}`;
383
+ document.getElementById('delivered').textContent = `${obs.packages_delivered} / ${obs.total_packages}`;
384
+
385
+ // Get cumulative reward from state endpoint
386
+ fetch(`${baseUrl}/state`)
387
+ .then(r => r.json())
388
+ .then(state => {
389
+ document.getElementById('reward').textContent = state.cum_reward.toFixed(1);
390
+ });
391
+ }
392
+
393
+ function updateStatsFromStep(data) {
394
+ const maxSteps = parseInt(document.getElementById('steps').textContent.split('/')[1].trim());
395
+ document.getElementById('steps').textContent = `${data.step_count} / ${maxSteps}`;
396
+ document.getElementById('delivered').textContent = data.packages_delivered + ' / ' + document.getElementById('delivered').textContent.split('/')[1].trim();
397
+
398
+ // Get cumulative reward from state endpoint
399
+ fetch(`${baseUrl}/state`)
400
+ .then(r => r.json())
401
+ .then(state => {
402
+ document.getElementById('reward').textContent = state.cum_reward.toFixed(1);
403
+ });
404
+ }
405
+
406
+ function showMessage(text, type) {
407
+ const messageDiv = document.getElementById('message');
408
+ messageDiv.textContent = text;
409
+ messageDiv.className = type;
410
+ messageDiv.classList.remove('hidden');
411
+ }
412
+
413
+ // Event listeners
414
+ document.getElementById('startBtn').addEventListener('click', startAutoPlay);
415
+ document.getElementById('stopBtn').addEventListener('click', stopAutoPlay);
416
+ document.getElementById('resetBtn').addEventListener('click', reset);
417
+ document.getElementById('resetManualBtn').addEventListener('click', reset);
418
+
419
+ // Initialize on load
420
+ reset();
421
+ </script>
422
+ </body>
423
+ </html>