SEMA / webapp /static /app.js
yunyixuan's picture
feat: translate Chinese knowledge outputs and hide keywords
8a69bbf
const analyzeForm = document.getElementById("analyze-form");
const chatForm = document.getElementById("chat-form");
const analyzeButton = document.getElementById("analyze-button");
const chatButton = document.getElementById("chat-button");
const videoFile = document.getElementById("video-file");
const fileTrigger = document.getElementById("file-trigger");
const fileName = document.getElementById("file-name");
const languageSelect = document.getElementById("language");
const questionInput = document.getElementById("question");
const statusCard = document.getElementById("status-card");
const statusText = document.getElementById("status-text");
const scoresEl = document.getElementById("scores");
const assessmentEl = document.getElementById("assessment");
const chatLog = document.getElementById("chat-log");
const jobIdEl = document.getElementById("job-id");
let activeJobId = null;
let pollHandle = null;
function renderSelectedFile() {
const selected = videoFile.files && videoFile.files.length ? videoFile.files[0].name : "No file chosen";
fileName.textContent = selected;
}
function setStatus(kind, text) {
statusCard.className = `status-card ${kind}`;
statusText.textContent = text;
}
function resetResults() {
if (pollHandle) {
clearTimeout(pollHandle);
pollHandle = null;
}
activeJobId = null;
jobIdEl.textContent = "";
scoresEl.innerHTML = "";
assessmentEl.textContent = "Visible after analysis completes.";
assessmentEl.className = "answer-box empty";
chatLog.innerHTML = '<div class="message message-system">Once analysis finishes, you can continue the conversation with SEMA.</div>';
questionInput.disabled = true;
chatButton.disabled = true;
}
function renderScores(scores) {
const entries = [
["Overall", scores.total],
["Head", scores.head],
["Hand", scores.hand],
["Torso", scores.torso],
["Foot", scores.foot],
["Arm", scores.arm],
];
scoresEl.innerHTML = entries.map(([label, value]) => `
<div class="score-card">
<span>${label}</span>
<strong>${value ?? "-"}</strong>
</div>
`).join("");
}
function appendMessage(role, content) {
const node = document.createElement("div");
node.className = `message message-${role}`;
node.textContent = content;
chatLog.appendChild(node);
chatLog.scrollTop = chatLog.scrollHeight;
}
function renderCompleted(job) {
activeJobId = job.job_id;
jobIdEl.textContent = job.job_id;
renderScores(job.result.scores);
assessmentEl.textContent = job.result.assessment_text;
assessmentEl.className = "answer-box";
questionInput.disabled = false;
chatButton.disabled = false;
}
function renderAnalysisFailure(message) {
scoresEl.innerHTML = "";
assessmentEl.textContent = message;
assessmentEl.className = "answer-box";
chatLog.innerHTML = '<div class="message message-system">Analysis failed. Fix the issue above and retry the upload.</div>';
questionInput.disabled = true;
chatButton.disabled = true;
}
fileTrigger.addEventListener("click", () => {
videoFile.click();
});
videoFile.addEventListener("change", () => {
renderSelectedFile();
});
renderSelectedFile();
async function pollJob(jobId) {
if (pollHandle) {
clearTimeout(pollHandle);
}
try {
const response = await fetch(`/api/jobs/${jobId}`);
const job = await response.json();
if (!response.ok) {
throw new Error(job.detail || "Failed to poll job status.");
}
if (job.status === "queued" || job.status === "running") {
const fallbackText = job.status === "queued"
? "Job created. Waiting to start."
: "Analyzing video. Please wait.";
setStatus("running", job.status_message || fallbackText);
pollHandle = setTimeout(() => pollJob(jobId), 2500);
return;
}
if (job.status === "failed") {
const errorMessage = job.error || "Analysis failed.";
renderAnalysisFailure(errorMessage);
setStatus("error", errorMessage);
return;
}
renderCompleted(job);
setStatus("success", "Analysis complete. You can ask follow-up questions.");
pollHandle = null;
} catch (error) {
const errorMessage = error.message || "Analysis job failed.";
renderAnalysisFailure(errorMessage);
setStatus("error", errorMessage);
pollHandle = null;
}
}
analyzeForm.addEventListener("submit", async (event) => {
event.preventDefault();
if (!videoFile.files.length) {
setStatus("error", "Please choose a video file first.");
return;
}
resetResults();
analyzeButton.disabled = true;
setStatus("running", "Uploading file and creating job.");
const formData = new FormData();
formData.append("file", videoFile.files[0]);
formData.append("language", languageSelect.value);
try {
const response = await fetch("/api/jobs", {
method: "POST",
body: formData,
});
const payload = await response.json();
if (!response.ok) {
throw new Error(payload.detail || "Failed to create analysis job.");
}
setStatus("running", payload.status_message || "Upload complete. The job is now queued.");
pollJob(payload.job_id);
} catch (error) {
setStatus("error", error.message || "Upload failed.");
} finally {
analyzeButton.disabled = false;
}
});
chatForm.addEventListener("submit", async (event) => {
event.preventDefault();
const question = questionInput.value.trim();
if (!activeJobId) {
setStatus("error", "Complete the video analysis first.");
return;
}
if (!question) {
setStatus("error", "Please enter a question.");
return;
}
appendMessage("user", question);
questionInput.value = "";
questionInput.disabled = true;
chatButton.disabled = true;
setStatus("running", "SEMA is generating an answer.");
try {
const response = await fetch(`/api/jobs/${activeJobId}/chat`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ question }),
});
const payload = await response.json();
if (!response.ok) {
throw new Error(payload.detail || "Follow-up answer failed.");
}
appendMessage("assistant", payload.answer);
setStatus("success", "Answer complete. You can keep asking follow-up questions.");
} catch (error) {
appendMessage("assistant", `Answer failed: ${error.message || "Unknown error"}`);
setStatus("error", error.message || "Follow-up answer failed.");
} finally {
questionInput.disabled = false;
chatButton.disabled = false;
questionInput.focus();
}
});