trackwhat-small / src /js /lib /analysisController.js
eggman-poff's picture
Upload folder using huggingface_hub
947c106 verified
Raw
History Blame Contribute Delete
10.2 kB
// @ts-nocheck
import { captureModeSuffix, effectiveScanMs, formatScanInterval } from "./config.js";
import { applyAnalysisOutputs, getAnalyzeButton, readAnalysisRequestInputs, readResultSignature } from "./analysisDom.js";
import { updateSmartDelayFromLatestResult, updateCountdown } from "./timing.js";
function findAnalyzeDependency() {
const dependencies = window.gradio_config?.dependencies || [];
return dependencies.find((dependency) => dependency?.api_name === 'analyze_screenshot')
|| dependencies.find((dependency) => String(dependency?.api_name || '').includes('analyze_screenshot'))
|| dependencies[0]
|| {};
}
function makeSessionHash() {
return `trackwhat_${Date.now().toString(36)}_${Math.random().toString(36).slice(2)}`;
}
function getApiOrigin() {
return window.location.origin;
}
async function readJsonResponse(response) {
const payload = await response.json().catch(() => ({}));
if (!response.ok) {
const error = new Error(payload?.detail || payload?.error || `Analysis request failed (${response.status})`);
error.status = response.status;
error.payload = payload;
throw error;
}
return payload;
}
function shouldFallbackToQueue(error) {
const message = String(error?.message || error || '').toLowerCase();
return error?.status === 404
|| message.includes('queue')
|| message.includes('failed to fetch')
|| message.includes('direct http post');
}
async function callAnalyzeDirect(data) {
const dependency = findAnalyzeDependency();
const sessionHash = makeSessionHash();
const fnIndex = Number.isFinite(Number(dependency.id)) ? Number(dependency.id) : 0;
const triggerId = Array.isArray(dependency.targets?.[0]) ? dependency.targets[0][0] : undefined;
const response = await fetch(`${getApiOrigin()}/run/${String(dependency.api_name || 'analyze_screenshot').replace(/^\//, '')}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
data,
event_data: null,
fn_index: fnIndex,
session_hash: sessionHash,
...(triggerId !== undefined ? { trigger_id: triggerId } : {}),
}),
});
return readJsonResponse(response);
}
async function callAnalyzeQueue(data) {
const dependency = findAnalyzeDependency();
const sessionHash = makeSessionHash();
const fnIndex = Number.isFinite(Number(dependency.id)) ? Number(dependency.id) : 0;
const triggerId = Array.isArray(dependency.targets?.[0]) ? dependency.targets[0][0] : undefined;
const joinBody = {
data,
event_data: null,
fn_index: fnIndex,
session_hash: sessionHash,
};
if (triggerId !== undefined) joinBody.trigger_id = triggerId;
await readJsonResponse(await fetch(`${getApiOrigin()}/queue/join`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(joinBody),
}));
return new Promise((resolve, reject) => {
const events = new EventSource(`${getApiOrigin()}/queue/data?session_hash=${encodeURIComponent(sessionHash)}`);
const timeout = window.setTimeout(() => {
events.close();
reject(new Error('Queued analysis timed out.'));
}, 180000);
events.onmessage = (event) => {
let message = {};
try {
message = JSON.parse(event.data || '{}');
} catch (error) {
return;
}
if (message.msg === 'process_completed') {
window.clearTimeout(timeout);
events.close();
if (message.success === false || message.output?.error) {
reject(new Error(message.output?.error || 'Queued analysis failed.'));
return;
}
resolve(message.output || {});
}
};
events.onerror = () => {
window.clearTimeout(timeout);
events.close();
reject(new Error('Queued analysis connection failed.'));
};
});
}
async function callAnalyze(data) {
try {
return await callAnalyzeQueue(data);
} catch (error) {
if (!shouldFallbackToQueue(error)) throw error;
window['__trackwhatDesktopCaptureTrace']?.('analysis.request.queue-fallback', {
message: error?.message || String(error),
status: error?.status || null,
});
return callAnalyzeDirect(data);
}
}
export function createAnalysisController({ state, scheduleNextScan, setCaptureStatus, onAnalysisPendingChange }) {
function setAnalysisPending(pending) {
state.analysisPending = pending;
if (state.analysisGuardTimer) {
window.clearTimeout(state.analysisGuardTimer);
state.analysisGuardTimer = null;
}
if (pending) {
state.analysisStartedAt = Date.now();
state.analysisStartSignature = readResultSignature();
state.analysisSawResultChange = false;
} else {
state.analysisStartedAt = 0;
state.analysisStartSignature = '';
state.analysisSawResultChange = false;
}
const { button } = getAnalyzeButton();
if (button) button.dataset.analysisPending = pending ? 'true' : 'false';
const resultBox = document.getElementById('important-response');
if (resultBox) resultBox.dataset.analysisPending = pending ? 'true' : 'false';
const headerButton = document.getElementById('header-analyze-btn');
if (headerButton) headerButton.dataset.analysisPending = pending ? 'true' : 'false';
onAnalysisPendingChange?.(pending);
}
function retryAnalysisLater(message, delayMs = 1000) {
window['__trackwhatDesktopCaptureTrace']?.('analysis.retry', {
message, delayMs, active: state.active, analysisPending: state.analysisPending, nextScanAt: state.nextScanAt,
});
setCaptureStatus(message);
if (state.active) scheduleNextScan(delayMs);
}
function recoverStaleAnalysis(reason = 'manual') {
if (!state.analysisPending) return false;
window['__trackwhatDesktopCaptureTrace']?.('analysis.recover', { reason, startedAt: state.analysisStartedAt, pending: state.analysisPending });
setAnalysisPending(false);
if (state.active) scheduleNextScan();
return true;
}
function markAnalysisComplete() {
if (!state.analysisPending) return;
const rearmNextScan = state.analysisRearmNextScan !== false;
const manualOneShot = state.analysisOneShot === true;
if (state.analysisGuardTimer) {
window.clearTimeout(state.analysisGuardTimer);
state.analysisGuardTimer = null;
}
setAnalysisPending(false);
updateSmartDelayFromLatestResult();
if (state.active && rearmNextScan && !manualOneShot) {
const delayText = state.smartDelayMs ? ` Smart delay added ${Math.round(state.smartDelayMs / 1000)}s.` : '';
setCaptureStatus(`Desktop capture is live${captureModeSuffix()}. Base interval ${formatScanInterval(effectiveScanMs())}.${delayText}`);
scheduleNextScan();
}
state.analysisRearmNextScan = true;
state.analysisOneShot = false;
}
function waitForAnalysisCompletion() {
if (state.analysisCompletionTicker) {
window.clearInterval(state.analysisCompletionTicker);
state.analysisCompletionTicker = null;
}
const startedAt = state.analysisStartedAt || Date.now();
const maxWaitMs = Math.max(10000, effectiveScanMs() * 2 + (state.smartDelayMs || 0) + 2500);
state.analysisCompletionTicker = window.setInterval(() => {
if (!state.analysisPending || startedAt !== state.analysisStartedAt) {
if (state.analysisCompletionTicker) {
window.clearInterval(state.analysisCompletionTicker);
state.analysisCompletionTicker = null;
}
return;
}
const currentSignature = readResultSignature();
if (currentSignature && currentSignature !== state.analysisStartSignature) {
if (state.analysisCompletionTicker) {
window.clearInterval(state.analysisCompletionTicker);
state.analysisCompletionTicker = null;
}
markAnalysisComplete();
return;
}
if (Date.now() - startedAt > maxWaitMs) {
if (state.analysisCompletionTicker) {
window.clearInterval(state.analysisCompletionTicker);
state.analysisCompletionTicker = null;
}
markAnalysisComplete();
}
}, 250);
state.analysisGuardTimer = window.setTimeout(() => {
if (!state.analysisPending || startedAt !== state.analysisStartedAt) return;
markAnalysisComplete();
}, maxWaitMs);
}
function requestAnalysis(rearmNextScan = true) {
const { frameData, lockedTrackName, note, useHintOnce, visionModelId } = readAnalysisRequestInputs();
if (!frameData) {
retryAnalysisLater('Desktop frame is not ready yet. Next capture will retry.', 1000);
return;
}
window['__trackwhatDesktopCaptureTrace']?.('analysis.request.start', {
active: state.active,
analysisPending: state.analysisPending,
rearmNextScan,
scanMs: state.scanMs,
effectiveScanMs: effectiveScanMs(),
requestMode: 'direct-api',
});
state.analysisRearmNextScan = rearmNextScan;
state.analysisOneShot = !rearmNextScan;
setCaptureStatus('Analyzing current desktop capture...');
setAnalysisPending(true);
waitForAnalysisCompletion();
(async () => {
try {
const payload = await callAnalyze([frameData, lockedTrackName, note, useHintOnce, visionModelId]);
const outputs = Array.isArray(payload?.data) ? payload.data : [];
window['__trackwhatDesktopCaptureTrace']?.('analysis.request.success', {
outputs: outputs.length,
track: outputs[0] || '',
});
applyAnalysisOutputs(outputs);
markAnalysisComplete();
} catch (error) {
setAnalysisPending(false);
retryAnalysisLater(`Could not start analysis: ${error?.message || error}`, 1500);
window['__trackwhatDesktopCaptureTrace']?.('analysis.request.error', {
message: error?.message || String(error),
});
console.warn('Desktop capture analysis request failed:', error);
}
})();
}
return { getAnalyzeButton, readResultSignature, setAnalysisPending, retryAnalysisLater, recoverStaleAnalysis, markAnalysisComplete, waitForAnalysisCompletion, requestAnalysis };
}