annaaaddddd commited on
Commit
4c88d67
·
verified ·
1 Parent(s): bb8b213

New layout for demo

Browse files
Files changed (1) hide show
  1. src/display/demo_new.html +559 -453
src/display/demo_new.html CHANGED
@@ -1,234 +1,259 @@
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>World Model Closed Loop Framework</title>
7
- <style>
8
- * { margin: 0; padding: 0; box-sizing: border-box; }
9
- body { font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: transparent; min-height: 100vh; overflow-x: hidden; margin: 0; padding: 0; }
10
- .container { max-width: 1400px; margin: 0 auto; padding: 20px; }
11
- .header { text-align: center; color: #333; margin-bottom: 30px; }
12
- .header h1 { font-size: 2.5em; margin-bottom: 10px; color: #333; }
13
- .header p { font-size: 1.2em; opacity: .8; color: #666; }
14
- .demo-container { background: white; border-radius: 20px; padding: 30px; box-shadow: 0 10px 30px rgba(0,0,0,.1); display: flex; flex-direction: column; transition: all .3s ease; margin: 20px 0; }
15
- .demo-container.expanded { min-height: auto; }
16
- .control-panel { background: #f8f9fa; border-radius: 15px; padding: 20px; margin-bottom: 20px; border-left: 5px solid #4285f4; flex-shrink: 0; }
17
- .control-row { display: flex; gap: 20px; align-items: center; margin-bottom: 15px; }
18
- .control-row:last-child { margin-bottom: 0; }
19
- .control-label { font-weight: bold; min-width: 150px; color: #333; }
20
- .control-input { flex: 1; }
21
- select, input[type="text"] { width: 100%; padding: 10px; border: 2px solid #ddd; border-radius: 8px; font-size: 14px; transition: border-color .3s; }
22
- select:focus, input[type="text"]:focus { outline: none; border-color: #4285f4; }
23
- .start-btn { background: linear-gradient(135deg,#4285f4,#34a853)!important; color: white!important; border: none; padding: 12px 30px!important; border-radius: 25px!important; font-size: 16px!important; font-weight: bold!important; cursor: pointer; transition: transform .3s!important, box-shadow .3s!important; }
24
- .start-btn:hover { transform: translateY(-2px); box-shadow: 0 10px 20px rgba(66,133,244,.3); }
25
- .scenario-info { padding: 10px; background: #e8f4fd; border-radius: 8px; color: #333; }
26
-
27
- /* Timeline */
28
- .timeline-container { background: #f8f9fa; border-radius: 10px; padding: 15px; margin-bottom: 15px; flex-shrink: 0; display: none; }
29
- .timeline-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
30
- .timeline-title { font-weight: bold; color: #333; font-size: 1em; }
31
- .frame-counter { background: #4285f4; color: white; padding: 3px 12px; border-radius: 15px; font-size: .8em; }
32
- .timeline { position: relative; height: 40px; background: #e8eaed; border-radius: 20px; margin-bottom: 10px; overflow: hidden; }
33
- .timeline-progress { height: 100%; background: linear-gradient(90deg,#4285f4,#34a853); border-radius: 20px; transition: width .5s ease; position: relative; }
34
- .timeline-markers { position: absolute; top: 0; left: 0; right: 0; height: 100%; display: flex; align-items: center; padding: 0 15px; }
35
- .frame-marker { width: 24px; height: 24px; border-radius: 50%; background: white; border: 2px solid #e8eaed; display: flex; align-items: center; justify-content: center; font-size: 10px; font-weight: bold; cursor: pointer; transition: all .3s; position: absolute; color: #666; }
36
- .frame-marker:hover { transform: scale(1.1); box-shadow: 0 3px 8px rgba(0,0,0,.2); }
37
- .frame-marker.completed { border-color: #34a853; color: #34a853; background: #e8f5e8; }
38
- .frame-marker.current { border-color: #4285f4; color: white; background: #4285f4; animation: pulse 2s infinite; }
39
- @keyframes pulse { 0%,100%{transform:scale(1)} 50%{transform:scale(1.1)} }
40
- .navigation-controls { display: flex; justify-content: center; gap: 15px; }
41
- .nav-btn { background: #4285f4!important; color: white!important; border: none; padding: 10px 20px!important; border-radius: 20px!important; cursor: pointer!important; font-size: 14px!important; transition: background .3s!important; }
42
- .nav-btn:hover { background: #3367d6; }
43
- .nav-btn:disabled { background: #ccc; cursor: not-allowed; }
44
-
45
- /* Overview */
46
- .overview-panel { display: none; gap: 20px; margin-bottom: 20px; flex-shrink: 0; }
47
- .birds-eye-section, .results-section { flex: 1; background: white; border: 3px solid #e8eaed; border-radius: 15px; padding: 20px; display: flex; flex-direction: column; }
48
- .section-header { background: linear-gradient(135deg,#34a853,#4285f4); color: white; padding: 12px 20px; border-radius: 10px; text-align: center; font-weight: bold; margin-bottom: 15px; }
49
- .results-section .section-header { background: linear-gradient(135deg,#ea4335,#fbbc04); }
50
- .section-content { flex: 1; background: #f8f9fa; border-radius: 10px; display: flex; align-items: center; justify-content: center; color: #666; font-style: italic; height: 180px; border: 2px dashed #ddd; }
51
- .results-content { background: #f8f9fa; border-radius: 10px; padding: 15px; height: 180px; display: flex; flex-direction: column; justify-content: flex-start; align-items: center; text-align: center; overflow: hidden; }
52
- .final-status-display { padding: 8px 20px; border-radius: 20px; font-weight: bold; margin-bottom: 12px; font-size: 1em; flex-shrink: 0; }
53
- .final-status-display.success { background: #34a853 !important; color: white !important; }
54
- .final-status-display.failure { background: #ea4335 !important; color: white !important; }
55
- .final-status-display.pending { background: #e8eaed !important; color: #666 !important; }
56
- .results-stats { font-size: .9em; color: #666; line-height: 1.5; }
57
-
58
- /* Main */
59
- .main-content { flex: 1; min-height: 0; display: flex; flex-direction: column; }
60
- .iteration-display { background: white; border: 3px solid #e8eaed; border-radius: 15px; padding: 20px; flex: 1; display: flex; flex-direction: column; transition: all .5s ease; min-height: 400px; }
61
- .iteration-display.active { border-color: #4285f4; background: linear-gradient(135deg,rgba(66,133,244,.05),rgba(52,168,83,.05)); }
62
- .iteration-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding: 15px 20px; background: linear-gradient(135deg,#4285f4,#34a853); color: white; border-radius: 10px; flex-shrink: 0; }
63
- .iteration-title { font-size: 1.4em; font-weight: bold; }
64
- .iteration-status { padding: 5px 15px; border-radius: 20px; background: rgba(255,255,255,.2); font-size: .9em; }
65
-
66
- /* NEW: Sequential Processing Indicator */
67
- .sequence-indicator {
68
- text-align: center;
69
- margin-bottom: 20px;
70
- padding: 15px;
71
- background: linear-gradient(135deg, #ff6b6b, #4ecdc4);
72
- border-radius: 15px;
73
- color: white;
74
- }
75
- .sequence-title {
76
- font-size: 1.2em;
77
- font-weight: bold;
78
- margin-bottom: 10px;
79
- }
80
- .sequence-flow {
81
- display: flex;
82
- justify-content: center;
83
- align-items: center;
84
- gap: 15px;
85
- flex-wrap: wrap;
86
- }
87
- .sequence-step {
88
- display: flex;
89
- align-items: center;
90
- background: rgba(255,255,255,0.15);
91
- padding: 8px 15px;
92
- border-radius: 20px;
93
- transition: all 0.3s;
94
- }
95
- .sequence-step.active {
96
- background: rgba(255,255,255,0.3);
97
- transform: scale(1.05);
98
- box-shadow: 0 4px 15px rgba(0,0,0,0.2);
99
- }
100
- .sequence-number {
101
- width: 25px;
102
- height: 25px;
103
- background: white;
104
- color: #333;
105
- border-radius: 50%;
106
- display: flex;
107
- align-items: center;
108
- justify-content: center;
109
- font-weight: bold;
110
- font-size: 14px;
111
- margin-right: 8px;
112
- }
113
- .sequence-arrow {
114
- color: white;
115
- font-size: 18px;
116
- margin: 0 5px;
117
- }
118
-
119
- .step-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; flex: 1; margin-bottom: 20px; }
120
- .step-card { background: white; border: 2px solid #e8eaed; border-radius: 12px; padding: 20px; transition: all .3s ease; display: flex; flex-direction: column; position: relative; }
121
- .step-card.active { border-color: #4285f4; transform: translateY(-5px); box-shadow: 0 10px 20px rgba(66,133,244,.15); }
122
-
123
- /* NEW: Processing animation for active step */
124
- .step-card.processing::before {
125
- content: '';
126
- position: absolute;
127
- top: -2px;
128
- left: -2px;
129
- right: -2px;
130
- bottom: -2px;
131
- background: linear-gradient(45deg, #4285f4, #34a853, #fbbc04, #ea4335);
132
- background-size: 300% 300%;
133
- border-radius: 12px;
134
- z-index: -1;
135
- animation: rainbow-border 2s linear infinite;
136
- }
137
- @keyframes rainbow-border {
138
- 0% { background-position: 0% 50%; }
139
- 50% { background-position: 100% 50%; }
140
- 100% { background-position: 0% 50%; }
141
- }
142
-
143
- .step-header { display: flex; align-items: center; margin-bottom: 15px; }
144
- .step-number { width: 30px; height: 30px; background: #4285f4; color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: bold; margin-right: 10px; position: relative; }
145
-
146
- /* NEW: Step number pulsing animation */
147
- .step-number.active-number {
148
- animation: number-pulse 1.5s ease-in-out infinite;
149
- background: linear-gradient(45deg, #4285f4, #34a853);
150
- }
151
- @keyframes number-pulse {
152
- 0%, 100% { transform: scale(1); }
153
- 50% { transform: scale(1.1); }
154
- }
155
-
156
- .step-title { font-weight: bold; color: #333; }
157
- .step-content { background: #f8f9fa; border-radius: 8px; padding: 12px; flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #666; font-style: italic; min-height: 100px; font-size: .9em; text-align: center; }
158
-
159
- /* Step 4 side column for Best Plan */
160
- .step4-wrap { display: grid; grid-template-columns: 0.6fr 0.4fr; gap: 14px; }
161
- .step4-card .step-content { align-items: center; justify-content: center; }
162
- .plan-list { display: flex; flex-direction: column; gap: 12px; }
163
- .plan-card { background: white; border: 2px solid #e8eaed; border-radius: 8px; padding: 15px; cursor: pointer; transition: all .3s; }
164
- .plan-card:hover { border-color: #4285f4; }
165
- .plan-card.selected { border-color: #34a853; background: rgba(52,168,83,.1); }
166
- .plan-title { font-weight: bold; margin-bottom: 8px; color: #333; }
167
- .confidence-bar { width: 100%; height: 8px; background: #e8eaed; border-radius: 4px; overflow: hidden; margin-top: 10px; }
168
- .confidence-fill { height: 100%; background: linear-gradient(90deg,#ea4335,#fbbc04,#34a853); transition: width .5s ease; }
169
-
170
- /* Responsive tweak */
171
- @media (max-width: 1100px) {
172
- .step4-wrap { grid-template-columns: 1fr; }
173
- .sequence-flow { flex-direction: column; }
174
- .sequence-arrow { transform: rotate(90deg); }
175
- }
176
- </style>
 
 
 
 
 
 
 
 
177
  </head>
178
  <body>
179
  <div class="container">
180
- <div class="header">
181
  <h1>🤖 World Model Closed Loop Framework</h1>
182
  <p>Navigate through iterations and explore the decision-making process</p>
183
- </div>
184
 
185
  <div class="demo-container">
186
- <div class="control-panel">
187
- <div class="control-row">
188
- <div class="control-label">Scenario:</div>
189
- <div class="control-input">
190
- <select id="scenarioSelect" onchange="loadScenario()">
191
- <option value="0">Kitchen Navigation - Find Red Cup</option>
192
- <option value="1">Living Room - Approach Blue Chair</option>
193
- <option value="2">Office Space - Navigate to Green Door</option>
194
- <option value="3">Bedroom - Find Yellow Lamp</option>
195
- </select>
 
 
 
 
196
  </div>
197
- </div>
198
- <div class="control-row">
199
- <div class="control-label">Target & Prompt:</div>
200
- <div class="control-input">
201
- <div id="scenarioInfo" class="scenario-info">🎯 Target: <strong>Red Cup (Kitchen Counter)</strong> | 📝 Prompt: "Navigate safely to the red cup on the kitchen counter"</div>
202
  </div>
203
- </div>
204
- <div class="control-row">
205
- <div class="control-label">Start Demo:</div>
206
- <div class="control-input">
207
- <button class="start-btn" onclick="startDemo()">🚀 Begin Closed Loop Process</button>
208
  </div>
209
  </div>
210
- </div>
211
 
212
- <div class="overview-panel" id="overviewPanel">
213
  <div class="birds-eye-section">
214
  <div class="section-header">🗺️ Bird's Eye View</div>
215
  <div class="section-content" id="birdEyeContent">
216
- <video id="birdEyeVideo" src="bev_video_5ZKStnWn8Zo.mp4" autoplay loop muted style="width:100%;max-height:200px;object-fit:contain;border-radius:8px;" controls>
 
 
 
 
217
  Your browser does not support the video tag.
218
  </video>
 
 
 
219
  </div>
220
  </div>
 
 
221
  <div class="results-section">
222
  <div class="section-header">📊 Final Results & Video</div>
223
  <div class="results-content" id="resultsContent">
224
- <div style="display:flex;align-items:center;gap:15px;height:100%;width:100%;">
225
  <div class="final-status-display pending" id="statusDisplay" style="margin-bottom:0;flex-shrink:0;">Processing...</div>
226
- <video id="finalVideo" src="https://huggingface.co/datasets/zonszer/demo_source_data/resolve/main/AR/FTwan21_lora/5ZKStnWn8Zo/E014/A001/world_model_gen/bbox_gen_video_1.mp4" controls style="flex:1;height:120px;border-radius:8px;display:none;"></video>
 
 
 
 
227
  </div>
228
  </div>
229
  </div>
230
  </div>
231
 
 
232
  <div class="timeline-container" id="timelineContainer">
233
  <div class="timeline-header">
234
  <div class="timeline-title">🎬 Navigation Timeline</div>
@@ -245,6 +270,7 @@
245
  </div>
246
  </div>
247
 
 
248
  <div class="main-content">
249
  <div class="iteration-display" id="iterationDisplay">
250
  <div class="iteration-header">
@@ -264,109 +290,173 @@ let isPlaying = false;
264
  let playInterval = null;
265
  let frames = [];
266
  let isRunning = false;
267
- let currentActiveStep = 0; // NEW: Track current processing step (1-4)
268
- let realActionData = {}; // Store loaded action data
 
 
269
 
270
  const scenarios = [
271
- { name: "Kitchen Navigation - Find Red Cup", target: "Red Cup (Kitchen Counter)", prompt: "Navigate safely to the red cup on the kitchen counter", frames: 8, environment: "Kitchen" },
272
  { name: "Living Room - Approach Blue Chair", target: "Blue Chair (Living Room)", prompt: "Move carefully to the blue armchair avoiding obstacles", frames: 12, environment: "Living Room" },
273
  { name: "Office Space - Navigate to Green Door", target: "Green Door (Exit)", prompt: "Navigate to the green emergency exit door", frames: 15, environment: "Office" },
274
  { name: "Bedroom - Find Yellow Lamp", target: "Yellow Lamp (Bedside)", prompt: "Approach the yellow bedside lamp without disturbing items", frames: 10, environment: "Bedroom" }
275
  ];
276
-
277
  let currentScenario = scenarios[0];
278
 
279
- // Load real action data from your huggingface dataset
280
  async function loadActionData() {
281
- const baseUrl = "https://huggingface.co/datasets/zonszer/demo_source_data/resolve/main/AR/FTwan21_lora/5ZKStnWn8Zo/E014";
282
- const frameIds = ["A000", "A001", "A002", "A003", "A004", "A005", "A006"];
283
-
284
  realActionData = {};
285
-
286
- for (let i = 0; i < frameIds.length; i++) {
287
  try {
288
- const response = await fetch(`${baseUrl}/${frameIds[i]}/action_plan.json`);
289
- if (response.ok) {
290
- const data = await response.json();
291
- realActionData[frameIds[i]] = data;
292
- console.log(`Loaded data for ${frameIds[i]}:`, data);
293
- } else {
294
- console.warn(`Could not load data for ${frameIds[i]}`);
295
- }
296
- } catch (error) {
297
- console.warn(`Error loading ${frameIds[i]}:`, error);
298
  }
299
- }
300
  }
301
 
302
- // Convert action plan data to display format
303
- function convertActionToPlans(actionData) {
304
- if (!actionData || !actionData.planner_data || !actionData.planner_data["planner_next-1.json"]) {
305
- return samplePlans; // Fallback to sample data
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
306
  }
307
-
308
- const planActions = actionData.planner_data["planner_next-1.json"];
309
- return planActions.map((action, index) => ({
310
- title: action.charAt(0).toUpperCase() + action.slice(1).replace(/([0-9.]+)m/, '$1m'),
311
- description: action,
312
- ranking: index + 1 // 1st, 2nd, 3rd choice based on original ordering
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
313
  }));
314
  }
315
 
316
- const samplePlans = [
317
- { title: "Go Straight", description: "go straight for 0.20m", ranking: 1 },
318
- { title: "Turn Right", description: "turn right 22.5 degrees", ranking: 2 },
319
- { title: "Turn Left", description: "turn left 22.5 degrees", ranking: 3 }
320
- ];
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
 
 
322
  function loadScenario(){
323
  const select = document.getElementById('scenarioSelect');
324
  currentScenario = scenarios[parseInt(select.value)];
325
- document.getElementById('scenarioInfo').innerHTML = `🎯 Target: <strong>${currentScenario.target}</strong> | 📝 Prompt: "${currentScenario.prompt}"`;
 
326
  totalFramesCount = currentScenario.frames;
 
327
  if (isRunning){
328
  if (isPlaying) togglePlayback();
329
  document.getElementById('timelineContainer').style.display = 'none';
330
  document.getElementById('overviewPanel').style.display = 'none';
331
  document.querySelector('.demo-container').classList.remove('expanded');
332
- isRunning = false;
333
- currentActiveStep = 0;
334
  const display = document.getElementById('iterationDisplay');
335
- display.innerHTML = `<div class="iteration-header"><div class="iteration-title">Ready to Start</div><div class="iteration-status">Waiting...</div></div><div style="flex:1;display:flex;align-items:center;justify-content:center;color:#666;font-size:1.2em;">👆 Click "Begin Closed Loop Process" to start the demonstration</div>`;
 
 
336
  display.classList.remove('active');
337
  }
338
  }
339
 
 
340
  async function startDemo(){
341
  if (isRunning) return;
342
-
343
- // Load real action data first
344
- console.log("Loading action data...");
345
  await loadActionData();
346
- console.log("Action data loaded:", realActionData);
347
-
348
- isRunning = true;
349
- currentActiveStep = 1;
350
  document.querySelector('.demo-container').classList.add('expanded');
351
- document.getElementById('overviewPanel').style.display = 'flex';
352
  document.getElementById('timelineContainer').style.display = 'block';
353
 
354
- const frameKeys = ["A000", "A001", "A002", "A003", "A004", "A005", "A006"];
355
-
356
  frames = Array.from({length: totalFramesCount}, (_, i) => {
357
  const frameKey = frameKeys[Math.min(i, frameKeys.length - 1)];
358
  const actionData = realActionData[frameKey];
359
-
360
  return {
361
  frameNumber: i + 1,
362
- frameKey: frameKey,
363
  status: i === 0 ? 'current' : 'pending',
364
  observation: `Frame ${i + 1} - ${currentScenario.environment} view`,
365
- plans: convertActionToPlans(actionData),
366
- selectedPlan: null,
 
367
  completed: false,
368
- currentStep: 0,
369
- actionData: actionData
370
  };
371
  });
372
 
@@ -374,80 +464,94 @@ async function startDemo(){
374
  document.getElementById('totalFrames').textContent = totalFramesCount;
375
  createTimeline();
376
  displayFrame(0);
377
- setTimeout(() => { processCurrentFrame(); }, 1000);
378
  }
379
 
 
380
  function processCurrentFrame(){
381
  const frame = frames[currentFrameIndex];
382
- if (frame.completed) return;
383
-
384
  frame.status = 'current';
385
- frame.currentStep = 1;
386
- currentActiveStep = 1;
387
- displayFrame(currentFrameIndex);
388
-
389
- // Step 1: Observation (1 second)
390
  setTimeout(() => {
391
- frame.currentStep = 2;
392
- currentActiveStep = 2;
393
- displayFrame(currentFrameIndex);
394
-
395
- // Step 2: VLM Planning (1.5 seconds)
396
  setTimeout(() => {
397
- frame.currentStep = 3;
398
- currentActiveStep = 3;
399
- displayFrame(currentFrameIndex);
400
-
401
- // Step 3: World Model Simulation (1.5 seconds)
402
  setTimeout(() => {
403
- frame.currentStep = 4;
404
- currentActiveStep = 4;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
405
  displayFrame(currentFrameIndex);
406
-
407
- // Step 4: Plan Selection (1 second)
408
- setTimeout(() => {
409
- const bestPlan = frame.plans[0]; // First plan is highest ranked
410
- selectPlan(currentFrameIndex, 0);
411
- frame.completed = true;
412
- frame.status = 'completed';
413
- frame.currentStep = 0;
414
- currentActiveStep = 0;
415
- displayFrame(currentFrameIndex);
416
- }, 1000);
417
- }, 1500);
418
- }, 1500);
419
- }, 1000);
420
  }
421
 
422
- function selectPlan(frameIndex, planIndex){
 
423
  const frame = frames[frameIndex];
424
- frame.selectedPlan = frame.plans[planIndex];
425
  displayFrame(frameIndex);
 
 
 
 
 
 
 
 
 
 
 
426
  }
427
 
428
- function jumpToFrame(frameIndex){
429
- if (isPlaying) togglePlayback();
430
- displayFrame(frameIndex);
431
- if (frames[frameIndex].status === 'pending' && frameIndex <= currentFrameIndex + 1){
432
- currentActiveStep = 1;
433
- processCurrentFrame();
 
 
434
  }
435
  }
436
-
437
- function previousFrame(){ if (currentFrameIndex > 0) displayFrame(currentFrameIndex - 1); }
438
- function nextFrame(){ if (currentFrameIndex < totalFramesCount - 1){ const nextIndex = currentFrameIndex + 1; displayFrame(nextIndex); if (frames[nextIndex].status === 'pending'){ currentActiveStep = 1; processCurrentFrame(); } } }
439
-
440
  function togglePlayback(){
441
  const playBtn = document.getElementById('playBtn');
442
- if (isPlaying){ clearInterval(playInterval); playBtn.textContent = '▶️ Play'; isPlaying = false; }
443
- else { playBtn.textContent = '⏸️ Pause'; isPlaying = true; playInterval = setInterval(() => { if (currentFrameIndex < totalFramesCount - 1) nextFrame(); else togglePlayback(); }, 3000); }
 
 
 
 
 
 
 
 
 
 
 
 
444
  }
445
-
446
  function updateNavigationButtons(){
447
  document.getElementById('prevBtn').disabled = currentFrameIndex === 0;
448
- document.getElementById('nextBtn').disabled = currentFrameIndex === totalFramesCount - 1;
449
  }
450
-
451
  function createTimeline(){
452
  const markersContainer = document.getElementById('timelineMarkers');
453
  markersContainer.innerHTML = '';
@@ -455,13 +559,12 @@ function createTimeline(){
455
  const marker = document.createElement('div');
456
  marker.className = 'frame-marker';
457
  marker.textContent = i + 1;
458
- marker.style.left = `${(i / (totalFramesCount - 1)) * 100}%`;
459
- marker.onclick = () => jumpToFrame(i);
460
  if (i === 0) marker.classList.add('current');
461
  markersContainer.appendChild(marker);
462
  }
463
  }
464
-
465
  function updateTimeline(){
466
  const progress = ((currentFrameIndex + 1) / totalFramesCount) * 100;
467
  document.getElementById('timelineProgress').style.width = progress + '%';
@@ -473,7 +576,13 @@ function updateTimeline(){
473
  else if (index === currentFrameIndex) marker.classList.add('current');
474
  });
475
  }
 
 
 
 
 
476
 
 
477
  function displayFrame(frameIndex){
478
  if (frameIndex < 0 || frameIndex >= totalFramesCount) return;
479
  currentFrameIndex = frameIndex;
@@ -483,178 +592,175 @@ function displayFrame(frameIndex){
483
  updateResults();
484
  const display = document.getElementById('iterationDisplay');
485
 
486
- // NEW: Create sequence indicator
 
 
487
  const sequenceIndicatorHTML = `
488
  <div class="sequence-indicator">
489
- <div class="sequence-title">🔄 Processing Sequence</div>
490
- <div class="sequence-flow">
491
- <div class="sequence-step ${frame.currentStep === 1 ? 'active' : ''}">
492
- <div class="sequence-number">1</div>
493
- <span>Observe</span>
494
- </div>
495
- <div class="sequence-arrow">→</div>
496
- <div class="sequence-step ${frame.currentStep === 2 ? 'active' : ''}">
497
- <div class="sequence-number">2</div>
498
- <span>Plan</span>
499
- </div>
500
- <div class="sequence-arrow">→</div>
501
- <div class="sequence-step ${frame.currentStep === 3 ? 'active' : ''}">
502
- <div class="sequence-number">3</div>
503
- <span>Simulate</span>
504
- </div>
505
- <div class="sequence-arrow">→</div>
506
- <div class="sequence-step ${frame.currentStep === 4 ? 'active' : ''}">
507
- <div class="sequence-number">4</div>
508
- <span>Select</span>
509
- </div>
510
- </div>
511
- </div>`;
512
 
513
- // Build the shared Step 4 + side column HTML
514
- const step4WithPlans = `
515
- <div class="step4-wrap">
516
- <div class="step-card ${(frame.status === 'current' || frame.completed) && frame.currentStep === 4 ? 'active processing' : (frame.status === 'current' || frame.completed) ? 'active' : ''} step4-card">
517
- <div class="step-header">
518
- <div class="step-number ${frame.currentStep === 4 ? 'active-number' : ''}">4</div>
519
- <div class="step-title">Best Plan Selection</div>
520
- </div>
521
- <div class="step-content">
522
- ⭐ ${frame.selectedPlan ? 'Plan Selected' : 'Evaluating options'}<br/>
523
- <small>${frame.selectedPlan ? frame.selectedPlan.title : 'Choosing optimal action'}</small>
524
- </div>
525
  </div>
526
- <div class="plan-list">
527
- ${frame.plans.map((plan, idx) => `
528
- <div class="plan-card ${frame.selectedPlan === plan ? 'selected' : ''}" onclick="selectPlan(${frameIndex}, ${idx})">
529
- <div class="plan-title">${plan.title}</div>
530
- <div style="font-size:.9em;color:#666;margin-bottom:10px;">${plan.description}</div>
531
- <div style="text-align:center;padding:8px;background:#f0f0f0;border-radius:6px;font-weight:bold;color:#333;">
532
- ${plan.ranking === 1 ? '🥇 1st Choice' : plan.ranking === 2 ? '🥈 2nd Choice' : '🥉 3rd Choice'}
533
- </div>
534
- </div>
535
- `).join('')}
536
  </div>
537
  </div>`;
538
 
539
- if (frameIndex === 0){
540
- display.innerHTML = `
541
- <div class="iteration-header">
542
- <div class="iteration-title">Frame ${frame.frameNumber} - Closed Loop Iteration (${frame.frameKey})</div>
543
- <div class="iteration-status">${frame.completed ? 'Completed' : frame.status === 'current' ? 'Processing...' : 'Ready'}</div>
 
 
 
 
 
544
  </div>
545
- ${frame.status === 'current' ? sequenceIndicatorHTML : ''}
546
- <div class="step-grid">
547
- <div class="step-card ${(frame.status === 'current' || frame.completed) && frame.currentStep === 1 ? 'active processing' : (frame.status === 'current' || frame.completed) ? 'active' : ''}">
548
- <div class="step-header">
549
- <div class="step-number ${frame.currentStep === 1 ? 'active-number' : ''}">1</div>
550
- <div class="step-title">Current Observation</div>
551
- </div>
552
- <div class="step-content" style="padding:8px;">
553
- <img src="https://huggingface.co/datasets/zonszer/demo_source_data/resolve/main/AR/FTwan21_lora/5ZKStnWn8Zo/E014/${frame.frameKey}/real_obs_bbox.png" alt="Current Observation" style="width:100%;max-height:200px;object-fit:contain;border-radius:6px;margin-bottom:8px;"/>
554
- <small style="color:#666;">First-Person Camera View with Bounding Boxes</small>
555
- </div>
556
- </div>
557
- <div class="step-card ${(frame.status === 'current' || frame.completed) && frame.currentStep === 2 ? 'active processing' : (frame.status === 'current' || frame.completed) ? 'active' : ''}">
558
- <div class="step-header">
559
- <div class="step-number ${frame.currentStep === 2 ? 'active-number' : ''}">2</div>
560
- <div class="step-title">VLM Planning</div>
561
- </div>
562
- <div class="step-content">🧠 Generated ${frame.plans.length} plans<br/><small>Target: ${currentScenario.target}</small></div>
563
  </div>
564
- <div class="step-card ${(frame.status === 'current' || frame.completed) && frame.currentStep === 3 ? 'active processing' : (frame.status === 'current' || frame.completed) ? 'active' : ''}">
565
- <div class="step-header">
566
- <div class="step-number ${frame.currentStep === 3 ? 'active-number' : ''}">3</div>
567
- <div class="step-title">World Model Simulation</div>
 
568
  </div>
569
- <div class="step-content" style="padding:8px;">
570
- <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:8px;">
571
- <div>
572
- <video src="https://huggingface.co/datasets/zonszer/demo_source_data/resolve/main/AR/FTwan21_lora/5ZKStnWn8Zo/E014/${frame.frameKey}/world_model_gen/obj_centered_gen_video.mp4" controls style="width:100%;height:140px;object-fit:contain;border-radius:4px;"></video>
573
- <div style="text-align:center;font-size:0.8em;color:#888;margin-top:4px;">Prediction 1</div>
574
- </div>
575
- <div>
576
- <video src="https://huggingface.co/datasets/zonszer/demo_source_data/resolve/main/AR/FTwan21_lora/5ZKStnWn8Zo/E014/${frame.frameKey}/world_model_gen/obj_centered_gen_video_1.mp4" controls style="width:100%;height:140px;object-fit:contain;border-radius:4px;"></video>
577
- <div style="text-align:center;font-size:0.8em;color:#888;margin-top:4px;">Prediction 2</div>
578
- </div>
579
- </div>
580
- <small style="color:#666;">Generated Future Views (Object-Centered)</small>
581
  </div>
582
  </div>
583
- ${step4WithPlans}
584
- </div>`;
585
- } else {
586
- display.innerHTML = `
587
- <div class="iteration-header">
588
- <div class="iteration-title">Frame ${frame.frameNumber} - Closed Loop Iteration (${frame.frameKey})</div>
589
- <div class="iteration-status">${frame.completed ? 'Completed' : frame.status === 'current' ? 'Processing...' : 'Ready'}</div>
590
  </div>
591
- ${frame.status === 'current' ? sequenceIndicatorHTML : ''}
592
- <div class="step-grid">
593
- <div class="step-card ${(frame.status === 'current' || frame.completed) && frame.currentStep === 1 ? 'active processing' : (frame.status === 'current' || frame.completed) ? 'active' : ''}">
594
- <div class="step-header">
595
- <div class="step-number ${frame.currentStep === 1 ? 'active-number' : ''}">1</div>
596
- <div class="step-title">Current Observation</div>
597
- </div>
598
- <div class="step-content" style="padding:8px;">
599
- <img src="https://huggingface.co/datasets/zonszer/demo_source_data/resolve/main/AR/FTwan21_lora/5ZKStnWn8Zo/E014/${frame.frameKey}/real_obs_bbox.png" alt="Current Observation" style="width:100%;max-height:200px;object-fit:contain;border-radius:6px;margin-bottom:8px;"/>
600
- <small style="color:#666;">First-Person Camera View with Bounding Boxes</small>
601
- </div>
602
- </div>
603
- <div class="step-card ${(frame.status === 'current' || frame.completed) && frame.currentStep === 2 ? 'active processing' : (frame.status === 'current' || frame.completed) ? 'active' : ''}">
604
- <div class="step-header">
605
- <div class="step-number ${frame.currentStep === 2 ? 'active-number' : ''}">2</div>
606
- <div class="step-title">VLM Planning</div>
607
- </div>
608
- <div class="step-content">🧠 Generated ${frame.plans.length} plans<br/><small>Target: ${currentScenario.target}</small></div>
609
- </div>
610
- <div class="step-card ${(frame.status === 'current' || frame.completed) && frame.currentStep === 3 ? 'active processing' : (frame.status === 'current' || frame.completed) ? 'active' : ''}">
611
- <div class="step-header">
612
- <div class="step-number ${frame.currentStep === 3 ? 'active-number' : ''}">3</div>
613
- <div class="step-title">World Model Simulation</div>
614
- </div>
615
- <div class="step-content" style="padding:8px;">
616
- <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-bottom:8px;">
617
- <div>
618
- <video src="https://huggingface.co/datasets/zonszer/demo_source_data/resolve/main/AR/FTwan21_lora/5ZKStnWn8Zo/E014/${frame.frameKey}/world_model_gen/obj_centered_gen_video.mp4" controls style="width:100%;height:140px;object-fit:contain;border-radius:4px;"></video>
619
- <div style="text-align:center;font-size:0.8em;color:#888;margin-top:4px;">Prediction 1</div>
620
- </div>
621
- <div>
622
- <video src="https://huggingface.co/datasets/zonszer/demo_source_data/resolve/main/AR/FTwan21_lora/5ZKStnWn8Zo/E014/${frame.frameKey}/world_model_gen/obj_centered_gen_video_1.mp4" controls style="width:100%;height:140px;object-fit:contain;border-radius:4px;"></video>
623
- <div style="text-align:center;font-size:0.8em;color:#888;margin-top:4px;">Prediction 2</div>
624
- </div>
625
- </div>
626
- <small style="color:#666;">Generated Future Views (Object-Centered)</small>
627
- </div>
628
  </div>
629
- ${step4WithPlans}
630
  </div>`;
631
  }
632
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
633
  if (frame.status === 'current' || frame.completed) display.classList.add('active');
634
  else display.classList.remove('active');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
635
  }
636
 
 
637
  function updateResults(){
638
  const completedFrames = frames.filter(f => f.completed).length;
639
- if (completedFrames === totalFramesCount){
640
- const success = Math.random() > 0.2;
641
- const statusElement = document.getElementById('statusDisplay');
642
- const finalVideo = document.getElementById('finalVideo');
643
- if (success){
644
- statusElement.textContent = 'Mission Success!';
645
- statusElement.className = 'final-status-display success';
646
- } else {
647
- statusElement.textContent = 'Mission Failed';
648
- statusElement.className = 'final-status-display failure';
649
  }
650
  finalVideo.style.display = 'block';
651
  } else if (completedFrames > 0){
652
- const statusElement = document.getElementById('statusDisplay');
653
  statusElement.textContent = 'In Progress...';
654
  statusElement.className = 'final-status-display pending';
655
- document.getElementById('finalVideo').style.display = 'none';
656
  }
657
  }
658
  </script>
659
  </body>
660
- </html>
 
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>World Model Closed Loop Framework</title>
7
+ <script>console.log("WMCL: 3-col header (Controls|BEV|Final) + 3-step sequence + merged Plan&Simulate + HEAD+Range probe");</script>
8
+ <style>
9
+ *{margin:0;padding:0;box-sizing:border-box}
10
+ /* 页面背景 + 顶部标题区文字颜色 */
11
+ body{
12
+ font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif;
13
+ background: transparent;
14
+ min-height:100vh;
15
+ overflow-x:hidden;
16
+ }
17
+ .container{
18
+ background: linear-gradient(135deg,#667eea 0%,#764ba2 100%); /* 把背景移到这里 */
19
+ min-height: auto;
20
+ padding: 20px; /* 如果需要的话可以添加一些内边距 */
21
+ }
22
+ .header{
23
+ text-align:center;
24
+ color:#fff !important; /* 标题与副标题用白色 */
25
+ margin-bottom:30px;
26
+ }
27
+ .header h1{
28
+ font-size:2.5em;
29
+ margin-bottom:10px;
30
+ text-shadow:2px 2px 4px rgba(0,0,0,.3); /* 轻微阴影让白字更清晰 */
31
+ }
32
+ .header p{
33
+ font-size:1.2em;
34
+ opacity:.9;
35
+ }
36
+
37
+ .demo-container{background:white;border-radius:20px;padding:30px;box-shadow:0 10px 30px rgba(0,0,0,.1);display:flex;flex-direction:column;transition:all .3s ease;margin:20px}
38
+ .demo-container.expanded{min-height:auto}
39
+
40
+ /* ===== 顶部三列并排:控制区 | BEV | Final ===== */
41
+ .overview-panel{
42
+ display:grid;
43
+ grid-template-columns: 1.15fr 1fr 1fr;
44
+ gap:20px;
45
+ margin-bottom:20px;
46
+ flex-shrink:0;
47
+ }
48
+
49
+ /* 控制区:与右侧卡片视觉统一 */
50
+ .control-panel{
51
+ background:white;
52
+ border:3px solid #e8eaed;
53
+ border-radius:15px;
54
+ padding:20px;
55
+ display:flex;flex-direction:column;
56
+ }
57
+ .control-row{display:flex;gap:20px;align-items:center;margin-bottom:15px}
58
+ .control-row:last-child{margin-bottom:0}
59
+ .control-label{font-weight:bold;min-width:150px;color:#333}
60
+ .control-input{flex:1}
61
+ select,input[type="text"]{width:100%;padding:10px;border:2px solid #ddd;border-radius:8px;font-size:14px;transition:border-color .3s}
62
+ select:focus,input[type="text"]:focus{outline:none;border-color:#4285f4}
63
+ .start-btn{background:linear-gradient(135deg,#4285f4,#34a853)!important;color:#fff!important;border:none;padding:12px 30px!important;border-radius:25px!important;font-size:16px!important;font-weight:bold!important;cursor:pointer;transition:transform .3s!important,box-shadow .3s!important}
64
+ .start-btn:hover{transform:translateY(-2px);box-shadow:0 10px 20px rgba(66,133,244,.3)}
65
+ .scenario-info{padding:10px;background:#e8f4fd;border-radius:8px;color:#333}
66
+
67
+ .birds-eye-section,.results-section{background:white;border:3px solid #e8eaed;border-radius:15px;padding:20px;display:flex;flex-direction:column}
68
+ .section-header{background:linear-gradient(135deg,#34a853,#4285f4);color:#fff;padding:12px 20px;border-radius:10px;text-align:center;font-weight:bold;margin-bottom:15px}
69
+ .results-section .section-header{background:linear-gradient(135deg,#ea4335,#fbbc04)}
70
+ .section-content{flex:1;background:#f8f9fa;border-radius:10px;display:flex;align-items:center;justify-content:center;color:#666;font-style:italic;height:180px;border:2px dashed #ddd}
71
+ .results-content{background:#f8f9fa;border-radius:10px;padding:15px;height:180px;display:flex;flex-direction:column;justify-content:flex-start;align-items:center;text-align:center;overflow:hidden}
72
+ .final-status-display{padding:8px 12px;border-radius:20px;font-weight:bold;margin-bottom:12px;font-size:1em;flex-shrink:0}
73
+ .final-status-display.success{background:#34a853!important;color:#fff!important}
74
+ .final-status-display.failure{background:#ea4335!important;color:#fff!important}
75
+ .final-status-display.pending{background:#e8eaed!important;color:#666!important}
76
+
77
+ /* ===== 时间线 ===== */
78
+ .timeline-container{background:#f8f9fa;border-radius:10px;padding:15px;margin-bottom:15px;flex-shrink:0;display: none;}
79
+ .timeline-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}
80
+ .timeline-title{font-weight:bold;color:#333;font-size:1em}
81
+ .frame-counter{background:#4285f4;color:#fff;padding:3px 12px;border-radius:15px;font-size:.8em}
82
+ .timeline{position:relative;height:40px;background:#e8eaed;border-radius:20px;margin-bottom:10px;overflow:hidden}
83
+ .timeline-progress{height:100%;background:linear-gradient(90deg,#4285f4,#34a853);border-radius:20px;transition:width .5s ease;position:relative}
84
+ .timeline-markers{position:absolute;top:0;left:0;right:0;height:100%;display:flex;align-items:center;padding:0 15px}
85
+ .frame-marker{width:24px;height:24px;border-radius:50%;background:#fff;border:2px solid #e8eaed;display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:bold;cursor:pointer;transition:all .3s;position:absolute;color:#666}
86
+ .frame-marker:hover{transform:scale(1.1);box-shadow:0 3px 8px rgba(0,0,0,.2)}
87
+ .frame-marker.completed{border-color:#34a853;color:#34a853;background:#e8f5e8}
88
+ .frame-marker.current{border-color:#4285f4;color:#fff;background:#4285f4;animation:pulse 2s infinite}
89
+ @keyframes pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.1)}}
90
+ .navigation-controls{display:flex;justify-content:center;gap:15px}
91
+ .nav-btn{background:#4285f4!important;color:#fff!important;border:none;padding:10px 20px!important;border-radius:20px!important;cursor:pointer!important;font-size:14px!important;transition:background .3s!important}
92
+ .nav-btn:hover{background:#3367d6}
93
+ .nav-btn:disabled{background:#ccc;cursor:not-allowed}
94
+
95
+ /* ===== 主区:三列(Step1 | Step2+3 | Step3[Select]) ===== */
96
+ .main-content{flex:1;min-height:0;display:flex;flex-direction:column}
97
+ .iteration-display{background:white;border:3px solid #e8eaed;border-radius:15px;padding:20px;flex:1;display:flex;flex-direction:column;transition:all .5s ease;min-height:400px}
98
+ .iteration-display.active{border-color:#4285f4;background:linear-gradient(135deg,rgba(66,133,244,.05),rgba(52,168,83,.05))}
99
+ .iteration-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;padding:15px 20px;background:linear-gradient(135deg,#4285f4,#34a853);color:#fff;border-radius:10px;flex-shrink:0}
100
+ .iteration-title{font-size:1.4em;font-weight:bold}
101
+ .iteration-status{padding:5px 15px;border-radius:20px;background:rgba(255,255,255,.2);font-size:.9em}
102
+
103
+ .step-grid{display:grid;grid-template-columns:1fr 1.3fr 1fr;gap:20px;flex:1;margin-bottom:20px}
104
+ .step-card{background:white;border:2px solid #e8eaed;border-radius:12px;padding:20px;transition:all .3s ease;display:flex;flex-direction:column;position:relative}
105
+ .step-card.active{border-color:#4285f4;transform:translateY(-5px);box-shadow:0 10px 20px rgba(66,133,244,.15)}
106
+ .step-card.processing::before{content:'';position:absolute;top:-2px;left:-2px;right:-2px;bottom:-2px;background:linear-gradient(45deg,#4285f4,#34a853,#fbbc04,#ea4335);background-size:300% 300%;border-radius:12px;z-index:-1;animation:rainbow-border 3s linear infinite;opacity:.8}
107
+ @keyframes rainbow-border{0%{background-position:0% 50%}50%{background-position:100% 50%}100%{background-position:0% 50%}}
108
+ .step-header{display:flex;align-items:center;margin-bottom:15px}
109
+ .step-number{width:30px;height:30px;background:#4285f4;color:#fff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-weight:bold;margin-right:10px;position:relative}
110
+ .step-number.active-number{animation:number-pulse 2s ease-in-out infinite;background:linear-gradient(45deg,#4285f4,#34a853)}
111
+ @keyframes number-pulse{0%,100%{transform:scale(1)}50%{transform:scale(1.08)}}
112
+ .step-title{font-weight:bold;color:#333}
113
+ .step-content{background:#f8f9fa;border-radius:8px;padding:12px;flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;color:#666;font-style:italic;min-height:100px;font-size:.9em;text-align:center}
114
+
115
+ /* Processing Sequence(圆角胶囊风 + 渐变底) */
116
+ .sequence-indicator{
117
+ text-align:center;
118
+ margin-bottom:20px;
119
+ padding:15px;
120
+ background:linear-gradient(135deg,#ff6b6b,#4ecdc4); /* 粉红→蓝绿 的渐变底 */
121
+ border-radius:15px;
122
+ color:#fff;
123
+ }
124
+ .sequence-title{
125
+ font-size:1.2em;
126
+ font-weight:bold;
127
+ margin-bottom:10px;
128
+ }
129
+ .sequence-flow{
130
+ display:flex;
131
+ justify-content:center;
132
+ align-items:center;
133
+ gap:15px;
134
+ flex-wrap:wrap;
135
+ }
136
+ .sequence-step{
137
+ display:flex;
138
+ align-items:center;
139
+ background:rgba(255,255,255,0.15);
140
+ padding:8px 15px;
141
+ border-radius:20px;
142
+ transition:all .3s;
143
+ }
144
+ .sequence-step.active{
145
+ background:rgba(255,255,255,0.3);
146
+ transform:scale(1.05);
147
+ box-shadow:0 4px 15px rgba(0,0,0,.2);
148
+ }
149
+ .sequence-number{
150
+ width:25px;height:25px;
151
+ background:#fff;color:#333;border-radius:50%;
152
+ display:flex;align-items:center;justify-content:center;
153
+ font-weight:bold;font-size:14px;margin-right:8px;
154
+ }
155
+ .sequence-arrow{ color:#fff; font-size:18px; margin:0 5px; }
156
+
157
+
158
+ /* Step2+3 合并内部布局 */
159
+ .plan-pairs{display:grid;grid-template-columns:1fr 1fr;gap:12px;width:100%}
160
+ .plan-box{border:1px dashed #ddd;border-radius:10px;padding:10px;background:#fff}
161
+ .sim-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px;width:100%}
162
+ .sim-grid video{width:100%;height:140px;object-fit:contain;border-radius:4px}
163
+ .subsection-title{font-weight:700;color:#333;margin:6px 0 8px 0}
164
+
165
+ /* Step3(Select) 右侧 ranking */
166
+ .action-list{display:flex;flex-direction:column;gap:12px;width:100%}
167
+ .plan-card{background:white;border:2px solid #e8eaed;border-radius:8px;padding:15px;cursor:pointer;transition:all .3s}
168
+ .plan-card:hover{border-color:#4285f4}
169
+ .plan-card.selected{border-color:#34a853;background:rgba(52,168,83,.08)}
170
+ .plan-title{font-weight:bold;margin-bottom:6px;color:#333}
171
+ .confidence-bar{width:100%;height:8px;background:#eee;border-radius:5px;overflow:hidden}
172
+ .confidence-fill{height:100%;background:linear-gradient(90deg,#ea4335,#fbbc04,#34a853)}
173
+ .plan-card.just-selected{animation:pulseSel .9s ease-in-out 2}
174
+ @keyframes pulseSel{0%{transform:scale(1);box-shadow:0 0 0 rgba(52,168,83,0)}50%{transform:scale(1.02);box-shadow:0 0 18px rgba(52,168,83,.4)}100%{transform:scale(1);box-shadow:0 0 0 rgba(52,168,83,0)}}
175
+
176
+ .success-chip{margin-left:8px;display:inline-flex;align-items:center;gap:6px;background:#e8f5e9;color:#1e4620;border:1px solid #c6e6c9;border-radius:18px;padding:3px 8px;font-weight:600}
177
+
178
+ @media (max-width:1100px){
179
+ .overview-panel{grid-template-columns:1fr}
180
+ .step-grid{grid-template-columns:1fr}
181
+ .plan-pairs{grid-template-columns:1fr}
182
+ .sim-grid{grid-template-columns:1fr}
183
+ }
184
+ </style>
185
  </head>
186
  <body>
187
  <div class="container">
188
+ <!-- <div class="header">
189
  <h1>🤖 World Model Closed Loop Framework</h1>
190
  <p>Navigate through iterations and explore the decision-making process</p>
191
+ </div> -->
192
 
193
  <div class="demo-container">
194
+ <!-- 顶部:控制区 | BEV | Final 并排 -->
195
+ <div class="overview-panel" id="overviewPanel">
196
+ <!-- 左列:控制区 -->
197
+ <div class="control-panel">
198
+ <div class="control-row">
199
+ <div class="control-label">Scenario:</div>
200
+ <div class="control-input">
201
+ <select id="scenarioSelect" onchange="loadScenario()">
202
+ <option value="0">Kitchen Navigation - Find Table</option>
203
+ <option value="1">Living Room - Approach Blue Chair</option>
204
+ <option value="2">Office Space - Navigate to Green Door</option>
205
+ <option value="3">Bedroom - Find Yellow Lamp</option>
206
+ </select>
207
+ </div>
208
  </div>
209
+ <div class="control-row">
210
+ <div class="control-label">Target & Prompt:</div>
211
+ <div class="control-input">
212
+ <div id="scenarioInfo" class="scenario-info">🎯 Target: <strong>Table (Kitchen)</strong> | 📝 Prompt: "Navigate safely to the table in the kitchen"</div>
213
+ </div>
214
  </div>
215
+ <div class="control-row">
216
+ <div class="control-label">Start Demo:</div>
217
+ <div class="control-input">
218
+ <button class="start-btn" onclick="startDemo()">🚀 Begin Closed Loop Process</button>
219
+ </div>
220
  </div>
221
  </div>
 
222
 
223
+ <!-- 中列:BEV -->
224
  <div class="birds-eye-section">
225
  <div class="section-header">🗺️ Bird's Eye View</div>
226
  <div class="section-content" id="birdEyeContent">
227
+ <video id="birdEyeVideo"
228
+ src="https://huggingface.co/spaces/WiW-collab/wiw-prototype/resolve/main/src/display/bev_video_5ZKStnWn8Zo.mp4"
229
+ autoplay loop muted controls
230
+ style="width:100%;max-height:200px;object-fit:contain;border-radius:8px;"
231
+ onerror="this.style.display='none'; this.nextElementSibling.style.display='block';">
232
  Your browser does not support the video tag.
233
  </video>
234
+ <div style="display:none;color:#999;text-align:center;padding:20px;font-style:italic;">
235
+ 🎥 BEV Video Loading...
236
+ </div>
237
  </div>
238
  </div>
239
+
240
+ <!-- 右列:Final -->
241
  <div class="results-section">
242
  <div class="section-header">📊 Final Results & Video</div>
243
  <div class="results-content" id="resultsContent">
244
+ <div style="display:flex;align-items:center;gap:10px;height:100%;width:100%;">
245
  <div class="final-status-display pending" id="statusDisplay" style="margin-bottom:0;flex-shrink:0;">Processing...</div>
246
+ <video id="finalVideo"
247
+ src="https://huggingface.co/datasets/zonszer/demo_source_data/resolve/main/AR/FTwan21_lora/5ZKStnWn8Zo/E014/A001/world_model_gen/bbox_gen_video_1.mp4"
248
+ controls
249
+ style="flex:1;height:120px;border-radius:8px;display:none;"
250
+ onerror="this.style.display='none'"></video>
251
  </div>
252
  </div>
253
  </div>
254
  </div>
255
 
256
+ <!-- 时间线 -->
257
  <div class="timeline-container" id="timelineContainer">
258
  <div class="timeline-header">
259
  <div class="timeline-title">🎬 Navigation Timeline</div>
 
270
  </div>
271
  </div>
272
 
273
+ <!-- 主展示 -->
274
  <div class="main-content">
275
  <div class="iteration-display" id="iterationDisplay">
276
  <div class="iteration-header">
 
290
  let playInterval = null;
291
  let frames = [];
292
  let isRunning = false;
293
+ let missionEnded = false;
294
+ let realActionData = {};
295
+
296
+ const DS_BASE = "https://huggingface.co/datasets/zonszer/demo_source_data/resolve/main/AR/FTwan21_lora/5ZKStnWn8Zo/E014";
297
 
298
  const scenarios = [
299
+ { name: "Kitchen Navigation - Find Table", target: "Table (Kitchen)", prompt: "Navigate safely to the table in the kitchen", frames: 8, environment: "Kitchen" },
300
  { name: "Living Room - Approach Blue Chair", target: "Blue Chair (Living Room)", prompt: "Move carefully to the blue armchair avoiding obstacles", frames: 12, environment: "Living Room" },
301
  { name: "Office Space - Navigate to Green Door", target: "Green Door (Exit)", prompt: "Navigate to the green emergency exit door", frames: 15, environment: "Office" },
302
  { name: "Bedroom - Find Yellow Lamp", target: "Yellow Lamp (Bedside)", prompt: "Approach the yellow bedside lamp without disturbing items", frames: 10, environment: "Bedroom" }
303
  ];
 
304
  let currentScenario = scenarios[0];
305
 
306
+ /* --------- 数据载入 --------- */
307
  async function loadActionData() {
308
+ const frameIds = ["A000","A001","A002","A003","A004","A005","A006","A007"];
 
 
309
  realActionData = {};
310
+ await Promise.all(frameIds.map(async id => {
 
311
  try {
312
+ const resp = await fetch(`${DS_BASE}/${id}/action_plan.json`, { method: 'GET', headers: { 'Accept': 'application/json' }, cache: 'no-store' });
313
+ if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
314
+ const data = JSON.parse(await resp.text());
315
+ realActionData[id] = data;
316
+ } catch (e) {
317
+ console.warn(`Load failed for ${id}:`, e);
318
+ realActionData[id] = null;
 
 
 
319
  }
320
+ }));
321
  }
322
 
323
+ /* Step2: 两个 plan(来自 planner_next-4.json) */
324
+ function getPlansFromNext4(actionData){
325
+ if (!actionData || !actionData.planner_data) return fallbackPlans();
326
+ const pd = actionData.planner_data;
327
+ let next4 = pd["planner_next-4.json"];
328
+ if (next4) {
329
+ if (Array.isArray(next4) && next4.length && Array.isArray(next4[0])) {
330
+ const seq1 = next4[0] || [];
331
+ const seq2 = next4[1] || [];
332
+ const p1 = { title: "Plan 1 (next-4)", steps: seq1, description: seq1.join(" → "), ranking: 1 };
333
+ const p2 = { title: "Plan 2 (next-4)", steps: seq2, description: seq2.join(" → "), ranking: 2 };
334
+ return [p1, p2].filter(p => p.steps && p.steps.length);
335
+ }
336
+ if (Array.isArray(next4)) {
337
+ const p1 = next4[0] ? { title:"Plan 1 (next-4)", steps:[next4[0]], description:String(next4[0]), ranking:1 } : null;
338
+ const p2 = next4[1] ? { title:"Plan 2 (next-4)", steps:[next4[1]], description:String(next4[1]), ranking:2 } : null;
339
+ return [p1,p2].filter(Boolean);
340
+ }
341
+ }
342
+ if (pd["planner_next-1.json"]) {
343
+ const arr = pd["planner_next-1.json"];
344
+ if (Array.isArray(arr)) {
345
+ const p1 = arr[0] ? { title:"Plan 1", steps:[arr[0]], description:String(arr[0]), ranking:1 } : null;
346
+ const p2 = arr[1] ? { title:"Plan 2", steps:[arr[1]], description:String(arr[1]), ranking:2 } : null;
347
+ return [p1,p2].filter(Boolean);
348
+ }
349
  }
350
+ return fallbackPlans();
351
+ }
352
+ function fallbackPlans(){
353
+ return [
354
+ { title:"Plan 1", steps:["go straight for 0.20m","go straight for 0.20m","turn right 22.5 degrees","go straight for 0.20m"], description:"go straight → go straight → turn right 22.5° → go straight", ranking:1 },
355
+ { title:"Plan 2", steps:["turn right 22.5 degrees","go straight for 0.20m","go straight for 0.20m","turn left 22.5 degrees"], description:"turn right 22.5° → go straight → go straight → turn left 22.5°", ranking:2 }
356
+ ];
357
+ }
358
+
359
+ /* Step3: 三个 action(planner_next-1.json)+ 占位 confidence 50/30/20 */
360
+ const PLACEHOLDER_CONF = {1:50,2:30,3:20};
361
+ function niceActionTitle(raw){
362
+ const s = String(raw||"").toLowerCase();
363
+ if (s.includes("go straight")) return "Go straight";
364
+ if (s.includes("turn left")) return "Turn left";
365
+ if (s.includes("turn right")) return "Turn right";
366
+ return raw || "Action";
367
+ }
368
+ function getRankedActionsFromNext1(actionData){
369
+ if (!actionData || !actionData.planner_data) return [];
370
+ const arr = actionData.planner_data["planner_next-1.json"];
371
+ if (!Array.isArray(arr) || arr.length===0) return [];
372
+ return arr.slice(0,3).map((a,idx)=>({
373
+ title: niceActionTitle(a),
374
+ ranking: idx+1,
375
+ conf: PLACEHOLDER_CONF[idx+1] || 20
376
  }));
377
  }
378
 
379
+ /* answerer_data top-1 */
380
+ function getTopAnswerer(actionData){
381
+ if (!actionData || !actionData.answerer_data) return {label:'—', score:0};
382
+ const entries = Object.entries(actionData.answerer_data);
383
+ if (!entries.length) return {label:'—', score:0};
384
+ entries.sort((a,b)=>b[1]-a[1]);
385
+ const [label,score] = entries[0];
386
+ return {label, score};
387
+ }
388
+ function pct(x){ return (Math.round(x*1000)/10).toFixed(1) + '%'; }
389
+
390
+ /* 仅 obj_centered:HEAD + Range GET 兜底;兼容 base/_1/_2 */
391
+ function buildObjCenteredOnly(frameKey) {
392
+ const base = `${DS_BASE}/${frameKey}/world_model_gen`;
393
+ const names = ["obj_centered_gen_video.mp4","obj_centered_gen_video_1.mp4","obj_centered_gen_video_2.mp4"];
394
+ const v = Date.now();
395
+ return names.map(n => `${base}/${n}?v=${v}`);
396
+ }
397
+ async function probe(url){
398
+ try{ const h = await fetch(url, { method:"HEAD", cache:"no-store" }); if (h.ok) return true; }catch{}
399
+ try{ const g = await fetch(url, { method:"GET", headers:{Range:"bytes=0-31"}, cache:"no-store" }); if (g.ok) return true; }catch{}
400
+ return false;
401
+ }
402
+ async function findUpToTwoVideos(frameKey) {
403
+ const candidates = buildObjCenteredOnly(frameKey);
404
+ const found = [];
405
+ for (const u of candidates) { if (await probe(u)) { found.push(u); if (found.length===2) break; } }
406
+ return found;
407
+ }
408
+ function setVideoSrcOrPlaceholder(videoEl, url) {
409
+ if (url) { videoEl.src = url; videoEl.load(); }
410
+ else { videoEl.replaceWith(document.createTextNode("Video unavailable")); }
411
+ }
412
 
413
+ /* --------- 场景控制 --------- */
414
  function loadScenario(){
415
  const select = document.getElementById('scenarioSelect');
416
  currentScenario = scenarios[parseInt(select.value)];
417
+ document.getElementById('scenarioInfo').innerHTML =
418
+ `🎯 Target: <strong>${currentScenario.target}</strong> | 📝 Prompt: "${currentScenario.prompt}"`;
419
  totalFramesCount = currentScenario.frames;
420
+
421
  if (isRunning){
422
  if (isPlaying) togglePlayback();
423
  document.getElementById('timelineContainer').style.display = 'none';
424
  document.getElementById('overviewPanel').style.display = 'none';
425
  document.querySelector('.demo-container').classList.remove('expanded');
426
+ isRunning = false; missionEnded = false;
 
427
  const display = document.getElementById('iterationDisplay');
428
+ display.innerHTML =
429
+ `<div class="iteration-header"><div class="iteration-title">Ready to Start</div><div class="iteration-status">Waiting...</div></div>
430
+ <div style="flex:1;display:flex;align-items:center;justify-content:center;color:#666;font-size:1.2em;">👆 Click "Begin Closed Loop Process" to start the demonstration</div>`;
431
  display.classList.remove('active');
432
  }
433
  }
434
 
435
+ /* --------- 生命周期 --------- */
436
  async function startDemo(){
437
  if (isRunning) return;
 
 
 
438
  await loadActionData();
439
+
440
+ isRunning = true; missionEnded = false;
 
 
441
  document.querySelector('.demo-container').classList.add('expanded');
442
+ document.getElementById('overviewPanel').style.display = 'grid';
443
  document.getElementById('timelineContainer').style.display = 'block';
444
 
445
+ const frameKeys = ["A000","A001","A002","A003","A004","A005","A006","A007"];
 
446
  frames = Array.from({length: totalFramesCount}, (_, i) => {
447
  const frameKey = frameKeys[Math.min(i, frameKeys.length - 1)];
448
  const actionData = realActionData[frameKey];
 
449
  return {
450
  frameNumber: i + 1,
451
+ frameKey,
452
  status: i === 0 ? 'current' : 'pending',
453
  observation: `Frame ${i + 1} - ${currentScenario.environment} view`,
454
+ plans: getPlansFromNext4(actionData), // Step2
455
+ actions3: getRankedActionsFromNext1(actionData), // Step3
456
+ selectedAction: null,
457
  completed: false,
458
+ currentStep: 0, // 1,2,3
459
+ actionData
460
  };
461
  });
462
 
 
464
  document.getElementById('totalFrames').textContent = totalFramesCount;
465
  createTimeline();
466
  displayFrame(0);
467
+ setTimeout(() => { processCurrentFrame(); }, 800);
468
  }
469
 
470
+ /* 核心流程(3 步) */
471
  function processCurrentFrame(){
472
  const frame = frames[currentFrameIndex];
473
+ if (frame.completed || missionEnded) return;
474
+
475
  frame.status = 'current';
476
+ frame.currentStep = 1; displayFrame(currentFrameIndex); // Observe
477
+
 
 
 
478
  setTimeout(() => {
479
+ frame.currentStep = 2; displayFrame(currentFrameIndex); // Plan & Simulate
 
 
 
 
480
  setTimeout(() => {
481
+ frame.currentStep = 3; displayFrame(currentFrameIndex); // Select
 
 
 
 
482
  setTimeout(() => {
483
+ const { score } = getTopAnswerer(frame.actionData);
484
+ if (score >= 0.95) {
485
+ const statusElement = document.getElementById('statusDisplay');
486
+ statusElement.innerHTML = 'Mission<br>Success!';
487
+ statusElement.className = 'final-status-display success';
488
+ document.getElementById('finalVideo').style.display = 'block';
489
+ missionEnded = true;
490
+ if (isPlaying) togglePlayback();
491
+ } else {
492
+ if (frame.actions3 && frame.actions3.length) {
493
+ frame.selectedAction = frame.actions3[0];
494
+ frame._pulseIdx = 0; // 渲染后触发动画
495
+ }
496
+ }
497
+ frame.completed = true;
498
+ frame.status = 'completed';
499
+ frame.currentStep = 0;
500
  displayFrame(currentFrameIndex);
501
+ }, 900);
502
+ }, 1200);
503
+ }, 800);
 
 
 
 
 
 
 
 
 
 
 
504
  }
505
 
506
+ /* 选择动作(点击) */
507
+ function selectAction(frameIndex, idx, animate=false){
508
  const frame = frames[frameIndex];
509
+ frame.selectedAction = (frame.actions3||[])[idx] || null;
510
  displayFrame(frameIndex);
511
+ if (animate) {
512
+ requestAnimationFrame(() => {
513
+ const cards = document.querySelectorAll('.action-list .plan-card');
514
+ const card = cards[idx];
515
+ if (card) {
516
+ card.classList.remove('just-selected');
517
+ void card.offsetWidth;
518
+ card.classList.add('just-selected');
519
+ }
520
+ });
521
+ }
522
  }
523
 
524
+ /* 时间线/播放 */
525
+ function previousFrame(){ if (!missionEnded && currentFrameIndex > 0) displayFrame(currentFrameIndex - 1); }
526
+ function nextFrame(){
527
+ if (missionEnded) return;
528
+ if (currentFrameIndex < totalFramesCount - 1){
529
+ const nextIndex = currentFrameIndex + 1;
530
+ displayFrame(nextIndex);
531
+ if (frames[nextIndex].status === 'pending'){ processCurrentFrame(); }
532
  }
533
  }
 
 
 
 
534
  function togglePlayback(){
535
  const playBtn = document.getElementById('playBtn');
536
+ if (isPlaying){
537
+ clearInterval(playInterval);
538
+ playBtn.textContent = '▶️ Play';
539
+ isPlaying = false;
540
+ } else {
541
+ if (missionEnded) return;
542
+ playBtn.textContent = '⏸️ Pause';
543
+ isPlaying = true;
544
+ playInterval = setInterval(() => {
545
+ if (missionEnded) { togglePlayback(); return; }
546
+ if (currentFrameIndex < totalFramesCount - 1) nextFrame();
547
+ else togglePlayback();
548
+ }, 2800);
549
+ }
550
  }
 
551
  function updateNavigationButtons(){
552
  document.getElementById('prevBtn').disabled = currentFrameIndex === 0;
553
+ document.getElementById('nextBtn').disabled = missionEnded || currentFrameIndex === totalFramesCount - 1;
554
  }
 
555
  function createTimeline(){
556
  const markersContainer = document.getElementById('timelineMarkers');
557
  markersContainer.innerHTML = '';
 
559
  const marker = document.createElement('div');
560
  marker.className = 'frame-marker';
561
  marker.textContent = i + 1;
562
+ marker.style.left = `${(i / Math.max(1,(totalFramesCount - 1))) * 100}%`;
563
+ marker.onclick = () => { if (!missionEnded) jumpToFrame(i); };
564
  if (i === 0) marker.classList.add('current');
565
  markersContainer.appendChild(marker);
566
  }
567
  }
 
568
  function updateTimeline(){
569
  const progress = ((currentFrameIndex + 1) / totalFramesCount) * 100;
570
  document.getElementById('timelineProgress').style.width = progress + '%';
 
576
  else if (index === currentFrameIndex) marker.classList.add('current');
577
  });
578
  }
579
+ function jumpToFrame(frameIndex){
580
+ if (isPlaying) togglePlayback();
581
+ displayFrame(frameIndex);
582
+ if (frames[frameIndex].status === 'pending' && frameIndex <= currentFrameIndex + 1){ processCurrentFrame(); }
583
+ }
584
 
585
+ /* --------- 渲染 --------- */
586
  function displayFrame(frameIndex){
587
  if (frameIndex < 0 || frameIndex >= totalFramesCount) return;
588
  currentFrameIndex = frameIndex;
 
592
  updateResults();
593
  const display = document.getElementById('iterationDisplay');
594
 
595
+ const {label:topLabel, score:topScore} = getTopAnswerer(frame.actionData);
596
+ const highConf = topScore >= 0.95;
597
+
598
  const sequenceIndicatorHTML = `
599
  <div class="sequence-indicator">
600
+ <div class="sequence-title">🔄 Processing Sequence</div>
601
+ <div class="sequence-flow">
602
+ <div class="sequence-step ${frame.currentStep === 1 ? 'active' : ''}"><div class="sequence-number">1</div><span>Observe</span></div>
603
+ <div class="sequence-arrow">→</div>
604
+ <div class="sequence-step ${frame.currentStep === 2 ? 'active' : ''}"><div class="sequence-number">2</div><span>Plan & Simulate</span></div>
605
+ <div class="sequence-arrow">→</div>
606
+ <div class="sequence-step ${frame.currentStep === 3 ? 'active' : ''}"><div class="sequence-number">3</div><span>Select</span></div>
607
+ </div>
608
+ </div>`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
609
 
610
+ const obsHTML = `
611
+ <div class="step-card ${(frame.status === 'current' || frame.completed) && frame.currentStep === 1 ? 'active processing' : (frame.status === 'current' || frame.completed) ? 'active' : ''}">
612
+ <div class="step-header">
613
+ <div class="step-number ${frame.currentStep === 1 ? 'active-number' : ''}">1</div>
614
+ <div class="step-title">Current Observation</div>
 
 
 
 
 
 
 
615
  </div>
616
+ <div class="step-content" style="padding:8px;">
617
+ <img src="${DS_BASE}/${frame.frameKey}/real_obs_bbox.png"
618
+ alt="Current Observation"
619
+ style="width:100%;max-height:200px;object-fit:contain;border-radius:6px;margin-bottom:8px;"
620
+ onerror="this.replaceWith(Object.assign(document.createElement('div'),{innerText:'Image unavailable',style:'color:#999;padding:8px'}))">
621
+ <small style="color:#666;">First-Person Camera View with Bounding Boxes</small>
 
 
 
 
622
  </div>
623
  </div>`;
624
 
625
+ /* 中列:Step 2 (Plan & Simulate) */
626
+ function planBox(p){
627
+ const items = (p?.steps||[]).slice(0,4).map(s=>`<li>${s}</li>`).join('') || '<li>—</li>';
628
+ return `<div class="plan-box"><div style="font-weight:600;margin-bottom:6px;color:#333">${p?.title||'Plan'}</div><ul style="text-align:left;list-style:disc;margin-left:18px;color:#555">${items}</ul></div>`;
629
+ }
630
+ const comboHTML = `
631
+ <div class="step-card ${(frame.status === 'current' || frame.completed) && frame.currentStep === 2 ? 'active processing' : (frame.status === 'current' || frame.completed) ? 'active' : ''}">
632
+ <div class="step-header">
633
+ <div class="step-number ${frame.currentStep === 2 ? 'active-number' : ''}">2</div>
634
+ <div class="step-title">Planning & World Model Simulation</div>
635
  </div>
636
+ <div class="step-content" style="gap:12px;">
637
+ <div class="plan-pairs">
638
+ ${planBox(frame.plans[0])}
639
+ ${planBox(frame.plans[1])}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
640
  </div>
641
+ <div class="subsection-title" style="margin-top:10px;">Object-centered Predictions</div>
642
+ <div class="sim-grid">
643
+ <div>
644
+ <video id="pred1_${frame.frameKey}" controls></video>
645
+ <div style="text-align:center;font-size:.8em;color:#888;margin-top:4px;">Prediction 1</div>
646
  </div>
647
+ <div>
648
+ <video id="pred2_${frame.frameKey}" controls></video>
649
+ <div style="text-align:center;font-size:.8em;color:#888;margin-top:4px;">Prediction 2</div>
 
 
 
 
 
 
 
 
 
650
  </div>
651
  </div>
 
 
 
 
 
 
 
652
  </div>
653
+ </div>`;
654
+
655
+ /* 右列:Step 3 (Select) */
656
+ function actionCardHTML(action, idx){
657
+ const p = action?.conf ?? (idx===0?50:idx===1?30:20);
658
+ const selected = frame.selectedAction === action ? 'selected' : '';
659
+ const onclick = highConf ? '' : `onclick="selectAction(${frameIndex}, ${idx}, true)"`;
660
+ return `
661
+ <div class="plan-card ${selected}" ${onclick}>
662
+ <div class="plan-title">${action?.title||'Action'}</div>
663
+ <div class="confidence-bar"><div class="confidence-fill" style="width:${p}%"></div></div>
664
+ <div style="display:flex;justify-content:space-between;margin-top:6px;font-size:.85em;color:#666;">
665
+ <span>${idx===0?'🥇 1st Choice':idx===1?'🥈 2nd Choice':'🥉 3rd Choice'}</span>
666
+ <span>${p}%</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
667
  </div>
 
668
  </div>`;
669
  }
670
 
671
+ const decisionExplain =
672
+ highConf
673
+ ? `<div style="padding:10px;border-radius:8px;background:#e8f5e9;color:#1e4620;border:1px solid #c6e6c9">
674
+ High confidence (≥95%) on <b>${topLabel}</b> → <b>Stop</b> selecting actions and declare success.
675
+ <span class="success-chip">✅ Success</span>
676
+ </div>`
677
+ : `<div style="padding:10px;border-radius:8px;background:#fff3cd;color:#7a5a00;border:1px solid #ffe69c">
678
+ Confidence is <b>${pct(topScore)}</b> &lt; 95% → choose the <b>highest-ranked</b> action.
679
+ </div>`;
680
+
681
+ const rightHTML = `
682
+ <div class="step-card ${(frame.status === 'current' || frame.completed) && frame.currentStep === 3 ? 'active processing' : (frame.status === 'current' || frame.completed) ? 'active' : ''}">
683
+ <div class="step-header">
684
+ <div class="step-number ${frame.currentStep === 3 ? 'active-number' : ''}">3</div>
685
+ <div class="step-title">Action Selection & Confidence</div>
686
+ </div>
687
+ <div class="step-content" style="gap:10px;">
688
+ <div style="font-style:normal;">
689
+ <b>Top object:</b> ${topLabel} &nbsp; <b>Confidence:</b> ${pct(topScore)}
690
+ </div>
691
+ ${decisionExplain}
692
+ ${highConf ? '' : `
693
+ <div class="action-list">
694
+ ${actionCardHTML(frame.actions3[0] || {title:'Go straight',conf:50}, 0)}
695
+ ${actionCardHTML(frame.actions3[1] || {title:'Turn right',conf:30}, 1)}
696
+ ${actionCardHTML(frame.actions3[2] || {title:'Turn left',conf:20}, 2)}
697
+ </div>
698
+ `}
699
+ </div>
700
+ </div>`;
701
+
702
+ display.innerHTML = `
703
+ <div class="iteration-header">
704
+ <div class="iteration-title">Frame ${frame.frameNumber} - Closed Loop Iteration (${frame.frameKey})</div>
705
+ <div class="iteration-status">${frame.completed ? 'Completed' : frame.status === 'current' ? 'Processing...' : 'Ready'}</div>
706
+ </div>
707
+ ${frame.status === 'current' ? sequenceIndicatorHTML : ''}
708
+ <div class="step-grid">
709
+ ${obsHTML}
710
+ ${comboHTML}
711
+ ${rightHTML}
712
+ </div>`;
713
+
714
+ // 初始化两个预测视频
715
+ (async () => {
716
+ try {
717
+ const k = frame.frameKey;
718
+ const v1 = document.getElementById(`pred1_${k}`);
719
+ const v2 = document.getElementById(`pred2_${k}`);
720
+ const urls = await findUpToTwoVideos(k);
721
+ if (v1) setVideoSrcOrPlaceholder(v1, urls[0] || null);
722
+ if (v2) setVideoSrcOrPlaceholder(v2, urls[1] || null);
723
+ } catch (e) { console.warn("init obj_centered videos failed", e); }
724
+ })();
725
+
726
  if (frame.status === 'current' || frame.completed) display.classList.add('active');
727
  else display.classList.remove('active');
728
+
729
+ // 自动选择的动效(未成功时)
730
+ if (typeof frame._pulseIdx === 'number') {
731
+ const idxToPulse = frame._pulseIdx;
732
+ requestAnimationFrame(() => {
733
+ requestAnimationFrame(() => {
734
+ const cards = document.querySelectorAll('.action-list .plan-card');
735
+ const card = cards[idxToPulse];
736
+ if (card) {
737
+ card.classList.remove('just-selected');
738
+ void card.offsetWidth;
739
+ card.classList.add('just-selected');
740
+ }
741
+ delete frame._pulseIdx;
742
+ });
743
+ });
744
+ }
745
  }
746
 
747
+ /* 结果区状态 */
748
  function updateResults(){
749
  const completedFrames = frames.filter(f => f.completed).length;
750
+ const statusElement = document.getElementById('statusDisplay');
751
+ const finalVideo = document.getElementById('finalVideo');
752
+ if (completedFrames === totalFramesCount || missionEnded){
753
+ if (!statusElement.className.includes('success')) {
754
+ statusElement.textContent = missionEnded ? statusElement.textContent : 'Mission Completed';
755
+ statusElement.className = missionEnded ? statusElement.className : 'final-status-display pending';
 
 
 
 
756
  }
757
  finalVideo.style.display = 'block';
758
  } else if (completedFrames > 0){
 
759
  statusElement.textContent = 'In Progress...';
760
  statusElement.className = 'final-status-display pending';
761
+ finalVideo.style.display = 'none';
762
  }
763
  }
764
  </script>
765
  </body>
766
+ </html>