Spaces:
Running
Running
Zhen Ye
Claude Opus 4.6
commited on
Commit
·
ba91677
1
Parent(s):
fe2ace8
Fix track cards stuck at UNASSESSED due to frontend race condition
Browse filessyncWithBackend (200ms) was overwriting GPT-enriched data that
pollAsyncJob (3s) had just merged. Now syncWithBackend preserves
cached GPT fields, the one-shot _gptEnriched gate is removed so
the poller can re-apply data each cycle, and an ASSESSED badge
fallback covers tracks with zero threat score.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- frontend/js/api/client.js +1 -2
- frontend/js/core/tracker.js +40 -0
- frontend/js/ui/cards.js +2 -0
frontend/js/api/client.js
CHANGED
|
@@ -184,8 +184,7 @@ APP.api.client.pollAsyncJob = async function () {
|
|
| 184 |
|
| 185 |
// Phase B: One-shot GPT feature merge (gated on gpt_raw + _gptEnriched flag)
|
| 186 |
const hasGptData = rawDets.some(d => d.gpt_raw);
|
| 187 |
-
if (hasGptData
|
| 188 |
-
state.hf._gptEnriched = true;
|
| 189 |
state.hf.firstFrameDetections = rawDets;
|
| 190 |
for (const rd of rawDets) {
|
| 191 |
const tid = rd.track_id || `T${String(rawDets.indexOf(rd) + 1).padStart(2, "0")}`;
|
|
|
|
| 184 |
|
| 185 |
// Phase B: One-shot GPT feature merge (gated on gpt_raw + _gptEnriched flag)
|
| 186 |
const hasGptData = rawDets.some(d => d.gpt_raw);
|
| 187 |
+
if (hasGptData) {
|
|
|
|
| 188 |
state.hf.firstFrameDetections = rawDets;
|
| 189 |
for (const rd of rawDets) {
|
| 190 |
const tid = rd.track_id || `T${String(rawDets.indexOf(rd) + 1).padStart(2, "0")}`;
|
frontend/js/core/tracker.js
CHANGED
|
@@ -235,6 +235,46 @@ APP.core.tracker.syncWithBackend = async function (frameIdx) {
|
|
| 235 |
};
|
| 236 |
});
|
| 237 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 238 |
// Update state
|
| 239 |
state.tracker.tracks = newTracks;
|
| 240 |
state.detections = newTracks; // Keep synced
|
|
|
|
| 235 |
};
|
| 236 |
});
|
| 237 |
|
| 238 |
+
// Preserve GPT enrichment data that syncWithBackend would otherwise wipe
|
| 239 |
+
const gptCache = state.hf.firstFrameDetections;
|
| 240 |
+
if (gptCache && gptCache.length > 0) {
|
| 241 |
+
const gptMap = {};
|
| 242 |
+
for (const gd of gptCache) {
|
| 243 |
+
const tid = gd.track_id || `T${String(gptCache.indexOf(gd) + 1).padStart(2, "0")}`;
|
| 244 |
+
if (gd.gpt_raw) gptMap[tid] = gd;
|
| 245 |
+
}
|
| 246 |
+
for (const track of newTracks) {
|
| 247 |
+
const cached = gptMap[track.id];
|
| 248 |
+
if (!cached || track.gpt_raw) continue;
|
| 249 |
+
const g = cached.gpt_raw;
|
| 250 |
+
track.gpt_raw = g;
|
| 251 |
+
track.assessment_status = cached.assessment_status || "ASSESSED";
|
| 252 |
+
track.threat_level_score = cached.threat_level_score || g.threat_level_score || 0;
|
| 253 |
+
track.threat_classification = cached.threat_classification || g.threat_classification || "Unknown";
|
| 254 |
+
track.weapon_readiness = cached.weapon_readiness || g.weapon_readiness || "Unknown";
|
| 255 |
+
track.gpt_distance_m = cached.gpt_distance_m || null;
|
| 256 |
+
track.gpt_direction = cached.gpt_direction || null;
|
| 257 |
+
track.mission_relevant = cached.mission_relevant ?? track.mission_relevant;
|
| 258 |
+
track.relevance_reason = cached.relevance_reason || track.relevance_reason;
|
| 259 |
+
track.features = {
|
| 260 |
+
"Type": g.object_type || "Unknown",
|
| 261 |
+
"Size": g.size || "Unknown",
|
| 262 |
+
"Threat Lvl": (g.threat_level || g.threat_level_score || "?") + "/10",
|
| 263 |
+
"Status": g.threat_classification || "?",
|
| 264 |
+
"Weapons": (g.visible_weapons || []).join(", ") || "None Visible",
|
| 265 |
+
"Readiness": g.weapon_readiness || "Unknown",
|
| 266 |
+
"Motion": g.motion_status || "Unknown",
|
| 267 |
+
"Range": g.range_estimate && g.range_estimate !== "Unknown"
|
| 268 |
+
? g.range_estimate + " (est.)" : "Unknown",
|
| 269 |
+
"Bearing": g.bearing || "Unknown",
|
| 270 |
+
"Intent": g.tactical_intent || "Unknown",
|
| 271 |
+
};
|
| 272 |
+
for (const feat of (g.dynamic_features || [])) {
|
| 273 |
+
if (feat && feat.key && feat.value) track.features[feat.key] = feat.value;
|
| 274 |
+
}
|
| 275 |
+
}
|
| 276 |
+
}
|
| 277 |
+
|
| 278 |
// Update state
|
| 279 |
state.tracker.tracks = newTracks;
|
| 280 |
state.detections = newTracks; // Keep synced
|
frontend/js/ui/cards.js
CHANGED
|
@@ -75,6 +75,8 @@ APP.ui.cards.renderFrameTrackList = function () {
|
|
| 75 |
statusBadge = '<span class="badgemini" style="background:#ffc107; color:#333">STALE</span>';
|
| 76 |
} else if (det.threat_level_score > 0) {
|
| 77 |
statusBadge = `<span class="badgemini" style="background:${det.threat_level_score >= 8 ? '#ff4d4d' : '#ff9f43'}; color:white">T-${det.threat_level_score}</span>`;
|
|
|
|
|
|
|
| 78 |
}
|
| 79 |
|
| 80 |
// GPT description (collapsed summary)
|
|
|
|
| 75 |
statusBadge = '<span class="badgemini" style="background:#ffc107; color:#333">STALE</span>';
|
| 76 |
} else if (det.threat_level_score > 0) {
|
| 77 |
statusBadge = `<span class="badgemini" style="background:${det.threat_level_score >= 8 ? '#ff4d4d' : '#ff9f43'}; color:white">T-${det.threat_level_score}</span>`;
|
| 78 |
+
} else if (assessStatus === "ASSESSED") {
|
| 79 |
+
statusBadge = '<span class="badgemini" style="background:#17a2b8; color:white">ASSESSED</span>';
|
| 80 |
}
|
| 81 |
|
| 82 |
// GPT description (collapsed summary)
|