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
+5 -4
View File
@@ -3,6 +3,7 @@ use strict;
use Dancer2; use Dancer2;
use Dancer2::Plugin::Database; use Dancer2::Plugin::Database;
use Data::Dumper; use Data::Dumper;
use JSON ();
use SQL::SplitStatement; use SQL::SplitStatement;
use HTTP::Lite; 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. # Accept both JSON body and form-encoded data for assign_key/results.
my ($assign_key, $results); my ($assign_key, $results);
my $ct = request_header('Content-Type') || ''; my $ct = request_header('Content-Type') || '';
if ($ct eq 'application/json') { if ($ct =~ m{application/json}i) {
my $body = decode_json(request->body()); my $body = JSON::decode_json(request->body());
$assign_key = $body->{assign_key}; $assign_key = $body->{assign_key};
$results = $body->{results}; $results = $body->{results};
} else { } else {
@@ -195,7 +196,7 @@ post '/api/v1/task/:task/complete' => sub {
} }
# Update the task as completed (store results as JSON text for MariaDB) # 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=?"); my $update_sth = database->prepare("UPDATE tasks SET status='COMPLETED', results=?, updated_at=NOW() WHERE id=?");
$update_sth->execute($results_json, $task_id); $update_sth->execute($results_json, $task_id);
if($update_sth->rows == 0) { if($update_sth->rows == 0) {
@@ -245,4 +246,4 @@ sub max {
sub min { sub min {
return (sort { $a <=> $b } @_)[0]; return (sort { $a <=> $b } @_)[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"
}'
+54 -20
View File
@@ -310,10 +310,17 @@ def api_submit_review(task_id):
return jsonify({"error": "verdict is required"}), 400 return jsonify({"error": "verdict is required"}), 400
try: try:
result = api_post(f"task/{task_id}/complete", { api_response = api_post(f"task/{task_id}/complete", {
"results": {"reviewer_verdict": verdict}, "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: except requests.RequestException as exc:
return jsonify({"error": f"API error: {exc}"}), 503 return jsonify({"error": f"API error: {exc}"}), 503
@@ -445,6 +452,7 @@ _REVIEW_TEMPLATE = """
.actions{display:flex;gap:12px;margin-top:14px} .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{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: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.false-positive:hover{border-color:#dc3545;color:#dc3545;background:#fff5f5}
.action-btn.correct:hover{border-color:#28a745;color:#28a745;background:#f0fff4} .action-btn.correct:hover{border-color:#28a745;color:#28a745;background:#f0fff4}
.skip-btn:hover{border-color:#6c757d;color:#6c757d;background:#f8f9fa} .skip-btn:hover{border-color:#6c757d;color:#6c757d;background:#f8f9fa}
@@ -453,6 +461,7 @@ _REVIEW_TEMPLATE = """
</style> </style>
</head> </head>
<body> <body>
<div id="flash"></div>
<div class="header"> <div class="header">
<h1>VideoDetect</h1> <h1>VideoDetect</h1>
<a href="/">&laquo; Back to queue</a> <a href="/">&laquo; Back to queue</a>
@@ -497,33 +506,58 @@ _REVIEW_TEMPLATE = """
<script> <script>
async function submitVerdict(verdict) { async function submitVerdict(verdict) {
const resp = await fetch(`/api/review/submit/{{ video.id }}`, { setActionButtonsDisabled(true);
method: 'POST', showFlash('Submitting verdict...', '#007bff');
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({verdict}) try {
}); const resp = await fetch(`/api/review/submit/{{ video.id }}`, {
if (resp.ok) { method: 'POST',
showFlash('Verdict submitted ✓', '#28a745'); headers: {'Content-Type': 'application/json'},
setTimeout(() => location.href = '/', 1000); body: JSON.stringify({verdict})
} else { });
const err = await resp.json(); const body = await resp.json().catch(() => ({}));
alert(err.error || 'Submission failed'); if (resp.ok) {
showFlash('Verdict submitted', '#28a745');
setTimeout(() => location.href = '/', 1000);
} else {
alert(body.error || 'Submission failed');
setActionButtonsDisabled(false);
}
} catch (err) {
alert(`Submission failed: ${err.message}`);
setActionButtonsDisabled(false);
} }
} }
async function skipTask() { async function skipTask() {
if (!confirm('Skip this task? It will be returned to the PENDING queue.')) return; if (!confirm('Skip this task? It will be returned to the PENDING queue.')) return;
const resp = await fetch(`/api/review/skip/{{ video.id }}`, {method: 'POST'}); setActionButtonsDisabled(true);
if (resp.ok) { showFlash('Skipping task...', '#6c757d');
showFlash('Task skipped, returned to queue', '#6c757d');
setTimeout(() => location.href = '/', 1000); try {
} else { const resp = await fetch(`/api/review/skip/{{ video.id }}`, {method: 'POST'});
alert('Skip failed'); 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) { function showFlash(msg, color) {
const el = document.getElementById('flash'); const el = document.getElementById('flash');
if (!el) return;
el.textContent = msg; el.style.background = color; el.style.display = 'block'; el.textContent = msg; el.style.background = color; el.style.display = 'block';
setTimeout(() => el.style.display = 'none', 2500); setTimeout(() => el.style.display = 'none', 2500);
} }