This commit is contained in:
Ryan Shpeherd
2026-09-15 13:59:41 -04:00
parent 75f38ec7ed
commit 8f2834775b
3 changed files with 64 additions and 24 deletions
+4 -3
View File
@@ -3,6 +3,7 @@ use strict;
use Dancer2;
use Dancer2::Plugin::Database;
use Data::Dumper;
use JSON ();
use SQL::SplitStatement;
use HTTP::Lite;
@@ -164,8 +165,8 @@ post '/api/v1/task/:task/complete' => sub {
# Accept both JSON body and form-encoded data for assign_key/results.
my ($assign_key, $results);
my $ct = request_header('Content-Type') || '';
if ($ct eq 'application/json') {
my $body = decode_json(request->body());
if ($ct =~ m{application/json}i) {
my $body = JSON::decode_json(request->body());
$assign_key = $body->{assign_key};
$results = $body->{results};
} else {
@@ -195,7 +196,7 @@ post '/api/v1/task/:task/complete' => sub {
}
# Update the task as completed (store results as JSON text for MariaDB)
my $results_json = defined $results ? encode_json($results) : 'null';
my $results_json = defined $results ? JSON::encode_json($results) : 'null';
my $update_sth = database->prepare("UPDATE tasks SET status='COMPLETED', results=?, updated_at=NOW() WHERE id=?");
$update_sth->execute($results_json, $task_id);
if($update_sth->rows == 0) {
Executable
+5
View File
@@ -0,0 +1,5 @@
#!/bin/bash
curl http://10.0.1.7:11434/api/generate -d '{
"model": "qwen3.6:35b-a3b",
"keep_alive": "60m"
}'
+40 -6
View File
@@ -310,10 +310,17 @@ def api_submit_review(task_id):
return jsonify({"error": "verdict is required"}), 400
try:
result = api_post(f"task/{task_id}/complete", {
"results": {"reviewer_verdict": verdict},
api_response = api_post(f"task/{task_id}/complete", {
"results": {
"review_verdict": verdict,
},
})
return jsonify({
"status": "completed",
"task_id": task_id,
"verdict": verdict,
"api_response": api_response,
})
return jsonify({"status": "completed", "task_id": task_id, "verdict": verdict})
except requests.RequestException as exc:
return jsonify({"error": f"API error: {exc}"}), 503
@@ -445,6 +452,7 @@ _REVIEW_TEMPLATE = """
.actions{display:flex;gap:12px;margin-top:14px}
.action-btn{flex:1;padding:14px;border:2px solid #dee2e6;border-radius:8px;background:#fff;cursor:pointer;font-size:15px;font-weight:600;transition:all .15s;text-align:center}
.action-btn:hover{border-color:#007bff;transform:translateY(-1px);box-shadow:0 2px 8px rgba(0,0,0,.1)}
.action-btn:disabled{opacity:.6;cursor:not-allowed;transform:none}
.action-btn.false-positive:hover{border-color:#dc3545;color:#dc3545;background:#fff5f5}
.action-btn.correct:hover{border-color:#28a745;color:#28a745;background:#f0fff4}
.skip-btn:hover{border-color:#6c757d;color:#6c757d;background:#f8f9fa}
@@ -453,6 +461,7 @@ _REVIEW_TEMPLATE = """
</style>
</head>
<body>
<div id="flash"></div>
<div class="header">
<h1>VideoDetect</h1>
<a href="/">&laquo; Back to queue</a>
@@ -497,33 +506,58 @@ _REVIEW_TEMPLATE = """
<script>
async function submitVerdict(verdict) {
setActionButtonsDisabled(true);
showFlash('Submitting verdict...', '#007bff');
try {
const resp = await fetch(`/api/review/submit/{{ video.id }}`, {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({verdict})
});
const body = await resp.json().catch(() => ({}));
if (resp.ok) {
showFlash('Verdict submitted ✓', '#28a745');
showFlash('Verdict submitted', '#28a745');
setTimeout(() => location.href = '/', 1000);
} else {
const err = await resp.json();
alert(err.error || 'Submission failed');
alert(body.error || 'Submission failed');
setActionButtonsDisabled(false);
}
} catch (err) {
alert(`Submission failed: ${err.message}`);
setActionButtonsDisabled(false);
}
}
async function skipTask() {
if (!confirm('Skip this task? It will be returned to the PENDING queue.')) return;
setActionButtonsDisabled(true);
showFlash('Skipping task...', '#6c757d');
try {
const resp = await fetch(`/api/review/skip/{{ video.id }}`, {method: 'POST'});
if (resp.ok) {
showFlash('Task skipped, returned to queue', '#6c757d');
setTimeout(() => location.href = '/', 1000);
} else {
alert('Skip failed');
setActionButtonsDisabled(false);
}
} catch (err) {
alert(`Skip failed: ${err.message}`);
setActionButtonsDisabled(false);
}
}
function setActionButtonsDisabled(disabled) {
document.querySelectorAll('.action-btn').forEach((button) => {
button.disabled = disabled;
});
}
function showFlash(msg, color) {
const el = document.getElementById('flash');
if (!el) return;
el.textContent = msg; el.style.background = color; el.style.display = 'block';
setTimeout(() => el.style.display = 'none', 2500);
}