Spaces:
Running
Running
Zhen Ye
commited on
Commit
·
56e4f43
1
Parent(s):
87cfe90
Refactor GPT sync in client.js: extract _syncGptFromDetections and add final sync on job completion
Browse files- frontend/js/api/client.js +84 -64
frontend/js/api/client.js
CHANGED
|
@@ -90,10 +90,85 @@ APP.api.client.cancelBackendJob = async function (jobId, reason) {
|
|
| 90 |
}
|
| 91 |
};
|
| 92 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
APP.api.client.pollAsyncJob = async function () {
|
| 94 |
const { state } = APP.core;
|
| 95 |
const { log, setHfStatus } = APP.ui.logging;
|
| 96 |
const { fetchProcessedVideo, fetchDepthVideo, fetchDepthFirstFrame } = APP.core.video;
|
|
|
|
| 97 |
|
| 98 |
const pollInterval = 3000; // 3 seconds
|
| 99 |
const maxAttempts = 200; // 10 minutes max
|
|
@@ -128,6 +203,14 @@ APP.api.client.pollAsyncJob = async function () {
|
|
| 128 |
log(`✓ Backend job ${completedJobId.substring(0, 8)}: completed successfully`, "g");
|
| 129 |
setHfStatus("job completed, fetching video...");
|
| 130 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 131 |
try {
|
| 132 |
await fetchProcessedVideo();
|
| 133 |
await fetchDepthVideo();
|
|
@@ -161,70 +244,7 @@ APP.api.client.pollAsyncJob = async function () {
|
|
| 161 |
|
| 162 |
// Check if GPT enrichment has updated first-frame detections
|
| 163 |
if (status.first_frame_detections && status.first_frame_detections.length > 0) {
|
| 164 |
-
|
| 165 |
-
let needsRender = false;
|
| 166 |
-
|
| 167 |
-
// Phase A: Always sync assessment status fields (not gated on gpt_raw)
|
| 168 |
-
for (const rd of rawDets) {
|
| 169 |
-
const tid = rd.track_id || `T${String(rawDets.indexOf(rd) + 1).padStart(2, "0")}`;
|
| 170 |
-
const existing = (state.detections || []).find(d => d.id === tid);
|
| 171 |
-
if (existing) {
|
| 172 |
-
if (rd.assessment_status && existing.assessment_status !== rd.assessment_status) {
|
| 173 |
-
existing.assessment_status = rd.assessment_status;
|
| 174 |
-
needsRender = true;
|
| 175 |
-
}
|
| 176 |
-
if (rd.mission_relevant !== undefined && rd.mission_relevant !== null) {
|
| 177 |
-
existing.mission_relevant = rd.mission_relevant;
|
| 178 |
-
}
|
| 179 |
-
if (rd.relevance_reason) {
|
| 180 |
-
existing.relevance_reason = rd.relevance_reason;
|
| 181 |
-
}
|
| 182 |
-
}
|
| 183 |
-
}
|
| 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")}`;
|
| 191 |
-
const existing = (state.detections || []).find(d => d.id === tid);
|
| 192 |
-
if (existing && rd.gpt_raw) {
|
| 193 |
-
const g = rd.gpt_raw;
|
| 194 |
-
const rangeStr = g.range_estimate && g.range_estimate !== "Unknown"
|
| 195 |
-
? g.range_estimate + " (est.)" : "Unknown";
|
| 196 |
-
existing.features = {
|
| 197 |
-
"Type": g.object_type || "Unknown",
|
| 198 |
-
"Size": g.size || "Unknown",
|
| 199 |
-
"Threat Lvl": (g.threat_level || g.threat_level_score || "?") + "/10",
|
| 200 |
-
"Status": g.threat_classification || "?",
|
| 201 |
-
"Weapons": (g.visible_weapons || []).join(", ") || "None Visible",
|
| 202 |
-
"Readiness": g.weapon_readiness || "Unknown",
|
| 203 |
-
"Motion": g.motion_status || "Unknown",
|
| 204 |
-
"Range": rangeStr,
|
| 205 |
-
"Bearing": g.bearing || "Unknown",
|
| 206 |
-
"Intent": g.tactical_intent || "Unknown",
|
| 207 |
-
};
|
| 208 |
-
const dynFeats = g.dynamic_features || [];
|
| 209 |
-
for (const feat of dynFeats) {
|
| 210 |
-
if (feat && feat.key && feat.value) {
|
| 211 |
-
existing.features[feat.key] = feat.value;
|
| 212 |
-
}
|
| 213 |
-
}
|
| 214 |
-
existing.threat_level_score = rd.threat_level_score || g.threat_level_score || 0;
|
| 215 |
-
existing.threat_classification = rd.threat_classification || g.threat_classification || "Unknown";
|
| 216 |
-
existing.weapon_readiness = rd.weapon_readiness || g.weapon_readiness || "Unknown";
|
| 217 |
-
existing.gpt_distance_m = rd.gpt_distance_m || null;
|
| 218 |
-
existing.gpt_direction = rd.gpt_direction || null;
|
| 219 |
-
needsRender = true;
|
| 220 |
-
}
|
| 221 |
-
}
|
| 222 |
-
log("Track cards updated with GPT assessment", "g");
|
| 223 |
-
}
|
| 224 |
-
|
| 225 |
-
if (needsRender && APP.ui && APP.ui.cards && APP.ui.cards.renderFrameTrackList) {
|
| 226 |
-
APP.ui.cards.renderFrameTrackList();
|
| 227 |
-
}
|
| 228 |
}
|
| 229 |
}
|
| 230 |
|
|
|
|
| 90 |
}
|
| 91 |
};
|
| 92 |
|
| 93 |
+
/**
|
| 94 |
+
* Sync GPT enrichment data from polled first_frame_detections into state.detections.
|
| 95 |
+
* Returns true if any card was updated and needs re-render.
|
| 96 |
+
*/
|
| 97 |
+
APP.api.client._syncGptFromDetections = function (rawDets, logLabel) {
|
| 98 |
+
const { state } = APP.core;
|
| 99 |
+
const { log } = APP.ui.logging;
|
| 100 |
+
let needsRender = false;
|
| 101 |
+
|
| 102 |
+
// Phase A: Sync assessment status, relevance fields
|
| 103 |
+
for (const rd of rawDets) {
|
| 104 |
+
const tid = rd.track_id || `T${String(rawDets.indexOf(rd) + 1).padStart(2, "0")}`;
|
| 105 |
+
const existing = (state.detections || []).find(d => d.id === tid);
|
| 106 |
+
if (existing) {
|
| 107 |
+
if (rd.assessment_status && existing.assessment_status !== rd.assessment_status) {
|
| 108 |
+
existing.assessment_status = rd.assessment_status;
|
| 109 |
+
needsRender = true;
|
| 110 |
+
}
|
| 111 |
+
if (rd.mission_relevant !== undefined && rd.mission_relevant !== null) {
|
| 112 |
+
existing.mission_relevant = rd.mission_relevant;
|
| 113 |
+
}
|
| 114 |
+
if (rd.relevance_reason) {
|
| 115 |
+
existing.relevance_reason = rd.relevance_reason;
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
// Phase B: Full GPT feature merge (gated on gpt_raw)
|
| 121 |
+
const hasGptData = rawDets.some(d => d.gpt_raw);
|
| 122 |
+
if (hasGptData) {
|
| 123 |
+
state.hf.firstFrameDetections = rawDets;
|
| 124 |
+
for (const rd of rawDets) {
|
| 125 |
+
const tid = rd.track_id || `T${String(rawDets.indexOf(rd) + 1).padStart(2, "0")}`;
|
| 126 |
+
const existing = (state.detections || []).find(d => d.id === tid);
|
| 127 |
+
if (existing && rd.gpt_raw) {
|
| 128 |
+
const g = rd.gpt_raw;
|
| 129 |
+
const rangeStr = g.range_estimate && g.range_estimate !== "Unknown"
|
| 130 |
+
? g.range_estimate + " (est.)" : "Unknown";
|
| 131 |
+
existing.features = {
|
| 132 |
+
"Type": g.object_type || "Unknown",
|
| 133 |
+
"Size": g.size || "Unknown",
|
| 134 |
+
"Threat Lvl": (g.threat_level || g.threat_level_score || "?") + "/10",
|
| 135 |
+
"Status": g.threat_classification || "?",
|
| 136 |
+
"Weapons": (g.visible_weapons || []).join(", ") || "None Visible",
|
| 137 |
+
"Readiness": g.weapon_readiness || "Unknown",
|
| 138 |
+
"Motion": g.motion_status || "Unknown",
|
| 139 |
+
"Range": rangeStr,
|
| 140 |
+
"Bearing": g.bearing || "Unknown",
|
| 141 |
+
"Intent": g.tactical_intent || "Unknown",
|
| 142 |
+
};
|
| 143 |
+
const dynFeats = g.dynamic_features || [];
|
| 144 |
+
for (const feat of dynFeats) {
|
| 145 |
+
if (feat && feat.key && feat.value) {
|
| 146 |
+
existing.features[feat.key] = feat.value;
|
| 147 |
+
}
|
| 148 |
+
}
|
| 149 |
+
existing.threat_level_score = rd.threat_level_score || g.threat_level_score || 0;
|
| 150 |
+
existing.threat_classification = rd.threat_classification || g.threat_classification || "Unknown";
|
| 151 |
+
existing.weapon_readiness = rd.weapon_readiness || g.weapon_readiness || "Unknown";
|
| 152 |
+
existing.gpt_distance_m = rd.gpt_distance_m || null;
|
| 153 |
+
existing.gpt_direction = rd.gpt_direction || null;
|
| 154 |
+
needsRender = true;
|
| 155 |
+
}
|
| 156 |
+
}
|
| 157 |
+
log(`Track cards updated with GPT assessment${logLabel ? " (" + logLabel + ")" : ""}`, "g");
|
| 158 |
+
}
|
| 159 |
+
|
| 160 |
+
if (needsRender && APP.ui && APP.ui.cards && APP.ui.cards.renderFrameTrackList) {
|
| 161 |
+
APP.ui.cards.renderFrameTrackList();
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
return needsRender;
|
| 165 |
+
};
|
| 166 |
+
|
| 167 |
APP.api.client.pollAsyncJob = async function () {
|
| 168 |
const { state } = APP.core;
|
| 169 |
const { log, setHfStatus } = APP.ui.logging;
|
| 170 |
const { fetchProcessedVideo, fetchDepthVideo, fetchDepthFirstFrame } = APP.core.video;
|
| 171 |
+
const syncGpt = APP.api.client._syncGptFromDetections;
|
| 172 |
|
| 173 |
const pollInterval = 3000; // 3 seconds
|
| 174 |
const maxAttempts = 200; // 10 minutes max
|
|
|
|
| 203 |
log(`✓ Backend job ${completedJobId.substring(0, 8)}: completed successfully`, "g");
|
| 204 |
setHfStatus("job completed, fetching video...");
|
| 205 |
|
| 206 |
+
// Final GPT sync — enrichment may have completed during
|
| 207 |
+
// processing but the poll never landed on a "processing"
|
| 208 |
+
// cycle that picked it up (common for segmentation mode
|
| 209 |
+
// where _enrich_first_frame_gpt is skipped).
|
| 210 |
+
if (status.first_frame_detections && status.first_frame_detections.length > 0) {
|
| 211 |
+
syncGpt(status.first_frame_detections, "final sync");
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
try {
|
| 215 |
await fetchProcessedVideo();
|
| 216 |
await fetchDepthVideo();
|
|
|
|
| 244 |
|
| 245 |
// Check if GPT enrichment has updated first-frame detections
|
| 246 |
if (status.first_frame_detections && status.first_frame_detections.length > 0) {
|
| 247 |
+
syncGpt(status.first_frame_detections);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 248 |
}
|
| 249 |
}
|
| 250 |
|