Compare commits

..

2 commits

Author SHA1 Message Date
hbrain
e691f61675 Permalink filter search 2026-06-05 01:51:37 +02:00
hbrain
ad184d778d Additional inputs for Describe functionality 2026-06-05 01:38:40 +02:00
6 changed files with 243 additions and 40 deletions

View file

@ -45,6 +45,134 @@
setDescribeButtonsDisabled(false); setDescribeButtonsDisabled(false);
} }
function showDescribePromptModal(job, articleId){
var existing = document.getElementById('mvlog-describe-prompt-modal');
if (existing) existing.remove();
var overlay = document.createElement('div');
overlay.id = 'mvlog-describe-prompt-modal';
overlay.style.cssText = 'position:fixed;left:0;top:0;width:100%;height:100%;background:rgba(0,0,0,0.5);display:flex;align-items:center;justify-content:center;z-index:9999;';
var box = document.createElement('div');
box.style.cssText = 'background:white;color:#111;padding:20px;max-width:800px;width:min(800px,92vw);max-height:80vh;overflow:auto;border-radius:6px;font-family:inherit;';
var h = document.createElement('h3');
h.textContent = 'Additional infos/guidelines';
var p = document.createElement('div');
p.style.cssText = 'margin:0 0 10px 0;color:#444;';
p.textContent = 'Optional extra guidance for the description prompt.';
var label = document.createElement('div');
label.style.cssText = 'font-weight:600;margin:10px 0 6px;';
label.textContent = 'Infos/guidelines';
var textarea = document.createElement('textarea');
textarea.rows = 8;
textarea.style.cssText = 'width:100%;box-sizing:border-box;font-family:inherit;padding:10px;border:1px solid #ccc;border-radius:4px;resize:vertical;';
textarea.placeholder = 'Loading saved prompt…';
textarea.disabled = true;
var btnBar = document.createElement('div');
btnBar.style.cssText = 'margin-top:12px;text-align:right;';
var submitBtn = document.createElement('button');
submitBtn.className = 'button';
submitBtn.textContent = 'Describe';
submitBtn.disabled = true;
var cancelBtn = document.createElement('button');
cancelBtn.className = 'button';
cancelBtn.textContent = 'Cancel';
cancelBtn.style.marginLeft = '8px';
btnBar.appendChild(submitBtn);
btnBar.appendChild(cancelBtn);
box.appendChild(h);
box.appendChild(p);
box.appendChild(label);
box.appendChild(textarea);
box.appendChild(btnBar);
overlay.appendChild(box);
document.body.appendChild(overlay);
function closeModal(){
if (overlay && overlay.parentNode) overlay.parentNode.removeChild(overlay);
document.removeEventListener('keydown', onKeyDown);
}
function submit(){
var promptText = textarea.value || '';
closeModal();
runDescribeRequest(job, articleId, promptText);
}
function onKeyDown(ev){
if (ev.key === 'Escape') {
ev.preventDefault();
closeModal();
} else if ((ev.ctrlKey || ev.metaKey) && ev.key === 'Enter') {
ev.preventDefault();
submit();
}
}
function loadSavedPrompt(){
var body = 'id=' + encodeURIComponent(articleId) + '&job=' + encodeURIComponent(job);
fetch('lib/describe_prompt.php', {
method: 'POST',
credentials: 'same-origin',
headers: {'Content-Type':'application/x-www-form-urlencoded'},
body: body
}).then(function(r){ return r.json().catch(function(){ return {}; }); })
.then(function(data){
if (!overlay || !overlay.parentNode) return;
if (data && data.status === 'ok' && typeof data.additional_prompt === 'string') {
textarea.value = data.additional_prompt;
}
}).catch(function(){}).finally(function(){
if (!overlay || !overlay.parentNode) return;
textarea.disabled = false;
submitBtn.disabled = false;
textarea.placeholder = 'Optional details about the images/videos/trip…';
setTimeout(function(){ textarea.focus(); }, 0);
});
}
cancelBtn.addEventListener('click', function(){ closeModal(); });
submitBtn.addEventListener('click', submit);
overlay.addEventListener('click', function(ev){ if (ev.target === overlay) closeModal(); });
document.addEventListener('keydown', onKeyDown);
loadSavedPrompt();
}
function runDescribeRequest(job, articleId, additionalPrompt){
showDescribeBusy();
var body = 'id=' + encodeURIComponent(articleId) + '&job=' + encodeURIComponent(job) + '&max_frames=16';
if (additionalPrompt && additionalPrompt.trim()) {
body += '&additional_prompt=' + encodeURIComponent(additionalPrompt);
}
fetch('lib/generate_data_sync.php', {
method: 'POST',
credentials: 'same-origin',
headers: {'Content-Type':'application/x-www-form-urlencoded'},
body: body
}).then(function(r){ return r.json().catch(()=>({})); })
.then(function(data){
if (data && data.status === 'ok'){
var desc = data.description || (data.raw_json && JSON.stringify(data.raw_json)) || '';
var teaser = data.teaser || '';
var rawFirst = (Array.isArray(data.raw_json) && data.raw_json.length) ? data.raw_json[0] : data.raw_json;
var quoteDa = data.quote_da || (rawFirst && rawFirst.quote_da) || '';
showModal(job, articleId, desc, teaser, quoteDa, data.raw_json || null);
} else {
var msg = (data && data.message) ? data.message : 'No response';
if (data && data.log) {
try {
var log = String(data.log);
var lines = log.split('\n').map(function(l){ return l.trim(); }).filter(function(l){ return l.length>0; });
var keywords = ['quota','rate limit','rate-limit','429','503','overloaded','high demand','busy','unavailable','error','failed','retry'];
var found = lines.find(function(l){ var ll=l.toLowerCase(); return keywords.some(function(k){ return ll.indexOf(k) !== -1; }); });
if (found) msg += ' — ' + found;
else if (lines.length) msg += ' — ' + lines[0].slice(0,200);
} catch(e){}
}
showToast('Error: ' + msg, false, (data && data.log) ? (typeof data.log === 'string' ? data.log : JSON.stringify(data.log)) : null);
}
}).catch(function(err){
showToast('Request failed: ' + err, false);
}).finally(function(){
hideDescribeBusy();
});
}
function showModal(job, articleId, description, teaser, quoteDa, rawJson){ function showModal(job, articleId, description, teaser, quoteDa, rawJson){
var existing = document.getElementById('mvlog-gemini-modal'); var existing = document.getElementById('mvlog-gemini-modal');
if (existing) existing.remove(); if (existing) existing.remove();
@ -127,39 +255,7 @@
var job = el.dataset.job || ''; var job = el.dataset.job || '';
var articleId = el.dataset.id || ''; var articleId = el.dataset.id || '';
if (!job && !articleId) return; if (!job && !articleId) return;
showDescribeBusy(); showDescribePromptModal(job, articleId);
fetch('lib/generate_data_sync.php', {
method: 'POST',
credentials: 'same-origin',
headers: {'Content-Type':'application/x-www-form-urlencoded'},
body: 'id=' + encodeURIComponent(articleId) + '&job=' + encodeURIComponent(job) + '&max_frames=16'
}).then(function(r){ return r.json().catch(()=>({})); })
.then(function(data){
if (data && data.status === 'ok'){
var desc = data.description || (data.raw_json && JSON.stringify(data.raw_json)) || '';
var teaser = data.teaser || '';
var rawFirst = (Array.isArray(data.raw_json) && data.raw_json.length) ? data.raw_json[0] : data.raw_json;
var quoteDa = data.quote_da || (rawFirst && rawFirst.quote_da) || '';
showModal(job, articleId, desc, teaser, quoteDa, data.raw_json || null);
} else {
var msg = (data && data.message) ? data.message : 'No response';
if (data && data.log) {
try {
var log = String(data.log);
var lines = log.split('\n').map(function(l){ return l.trim(); }).filter(function(l){ return l.length>0; });
var keywords = ['quota','rate limit','rate-limit','429','503','overloaded','high demand','busy','unavailable','error','failed','retry'];
var found = lines.find(function(l){ var ll=l.toLowerCase(); return keywords.some(function(k){ return ll.indexOf(k) !== -1; }); });
if (found) msg += ' — ' + found;
else if (lines.length) msg += ' — ' + lines[0].slice(0,200);
} catch(e){}
}
showToast('Error: ' + msg, false, (data && data.log) ? (typeof data.log === 'string' ? data.log : JSON.stringify(data.log)) : null);
}
}).catch(function(err){
showToast('Request failed: ' + err, false);
}).finally(function(){
hideDescribeBusy();
});
}); });
attachDescribeButtons(); attachDescribeButtons();

File diff suppressed because one or more lines are too long

View file

@ -4,10 +4,14 @@ set -euo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
STATE_FILE="${SCRIPT_DIR}/.personality_index" STATE_FILE="${SCRIPT_DIR}/.personality_index"
IN_DIR="${1:?Usage: $0 <input-dir> [\"Title\"] [\"Location\"] [max_frames]}" IN_DIR="${1:?Usage: $0 <input-dir> [\"Title\"] [\"Location\"] [max_frames] [additional_prompt]}"
TITLE="${2:-}" TITLE="${2:-}"
LOCATION="${3:-}" LOCATION="${3:-}"
MAX_FRAMES="${4:-12}" MAX_FRAMES="${4:-12}"
ADDITIONAL_PROMPT="${5:-}"
if [ "$ADDITIONAL_PROMPT" = "no-append" ]; then
ADDITIONAL_PROMPT=""
fi
# Source system-wide Gemini API key if present so the webserver can run this as www-data. # Source system-wide Gemini API key if present so the webserver can run this as www-data.
if [ -f /etc/mvlog/gemini.env ]; then if [ -f /etc/mvlog/gemini.env ]; then
@ -117,6 +121,15 @@ if [ -z "$TITLE" ]; then
TITLE="$(basename "$IN_DIR")" TITLE="$(basename "$IN_DIR")"
fi fi
ADDITIONAL_PROMPT_BLOCK=""
if [ -n "$ADDITIONAL_PROMPT" ]; then
ADDITIONAL_PROMPT_BLOCK=$(cat <<EOF
Additional infos/guidelines:
$ADDITIONAL_PROMPT
EOF
)
fi
PERSONALITIES=( PERSONALITIES=(
"You should talk like Shoresy: fearless Canadian hockey-player energy, confidence, chirping, competitiveness, profanity, loyalty, roasting, locker-room humor." "You should talk like Shoresy: fearless Canadian hockey-player energy, confidence, chirping, competitiveness, profanity, loyalty, roasting, locker-room humor."
@ -154,7 +167,7 @@ The Danish sentence must be witty, warm, cultured, playful, gently satirical, an
Use the following personality and write entirely in that style: Use the following personality and write entirely in that style:
"$PERSONALITY" "$PERSONALITY"
${ADDITIONAL_PROMPT_BLOCK}
Then create a teaser in English. Then create a teaser in English.
The teaser must be max. 32 words. The teaser must be max. 32 words.
It should make someone want to click and read more. It should make someone want to click and read more.

66
lib/describe_prompt.php Normal file
View file

@ -0,0 +1,66 @@
<?php
require __DIR__ . '/../auth.php';
mvlog_require_login();
$MVLOG_ROOT = dirname(__DIR__);
header('Content-Type: application/json; charset=utf-8');
$id = strtolower(trim((string)($_POST['id'] ?? $_GET['id'] ?? '')));
$job = trim((string)($_POST['job'] ?? $_GET['job'] ?? ''));
function mvlog_article_id_is_valid(string $id): bool {
return (bool)preg_match('/^[0-9]{14}[a-f0-9]{16}$/', $id);
}
function mvlog_legacy_job_is_valid(string $job): bool {
return (bool)preg_match('/^[0-9]{8}[-_][A-Za-z0-9._-]+$/', $job);
}
function mvlog_read_article_id(string $jobdir): string {
$path = $jobdir . '/.mvlog-id';
if (!is_file($path)) return '';
$value = strtolower(trim((string)@file_get_contents($path)));
return mvlog_article_id_is_valid($value) ? $value : '';
}
function mvlog_find_jobdir_by_id(string $mvlogRoot, string $articleId): ?string {
if (!mvlog_article_id_is_valid($articleId)) return null;
$cacheFile = $mvlogRoot . '/cache/articles-index.json';
if (is_file($cacheFile)) {
$cache = json_decode((string)@file_get_contents($cacheFile), true);
if (is_array($cache) && is_array($cache['by_id'] ?? null)) {
$name = (string)($cache['by_id'][$articleId] ?? '');
if ($name !== '') {
$candidate = $mvlogRoot . '/in-dir/' . basename($name);
if (is_dir($candidate)) return $candidate;
}
}
}
foreach (glob($mvlogRoot . '/in-dir/*', GLOB_ONLYDIR) ?: [] as $dir) {
if (mvlog_read_article_id($dir) === $articleId) return $dir;
}
return null;
}
$jobdir = null;
if ($id !== '' && mvlog_article_id_is_valid($id)) {
$jobdir = mvlog_find_jobdir_by_id($MVLOG_ROOT, $id);
}
if ($jobdir === null && $job !== '' && mvlog_legacy_job_is_valid($job)) {
$candidate = $MVLOG_ROOT . '/in-dir/' . basename($job);
if (is_dir($candidate)) $jobdir = $candidate;
}
if ($jobdir === null || !is_dir($jobdir)) {
http_response_code(404);
echo json_encode(['status' => 'error', 'message' => 'job not found']);
exit;
}
$path = $jobdir . '/.mvlog-describe-prompt.txt';
$prompt = is_file($path) ? trim((string)@file_get_contents($path)) : '';
echo json_encode(['status' => 'ok', 'additional_prompt' => $prompt], JSON_UNESCAPED_UNICODE);

View file

@ -9,6 +9,7 @@ header('Content-Type: application/json; charset=utf-8');
$job = $_POST['job'] ?? ''; $job = $_POST['job'] ?? '';
$max_frames = isset($_POST['max_frames']) ? intval($_POST['max_frames']) : 16; $max_frames = isset($_POST['max_frames']) ? intval($_POST['max_frames']) : 16;
$additional_prompt = trim((string)($_POST['additional_prompt'] ?? ''));
if (!preg_match('/^[0-9]{8}[-_][A-Za-z0-9._-]+$/', $job)) { if (!preg_match('/^[0-9]{8}[-_][A-Za-z0-9._-]+$/', $job)) {
http_response_code(400); http_response_code(400);
@ -23,6 +24,14 @@ if (!is_dir($jobdir)) {
exit; exit;
} }
$describe_prompt_file = $jobdir . '/.mvlog-describe-prompt.txt';
if ($additional_prompt !== '') {
@file_put_contents($describe_prompt_file, $additional_prompt . PHP_EOL);
@chmod($describe_prompt_file, 0664);
@chown($describe_prompt_file, 'www-data');
@chgrp($describe_prompt_file, 'www-data');
}
// Read existing data.txt for Title and Location (case-insensitive aliases) // Read existing data.txt for Title and Location (case-insensitive aliases)
$data_file = $jobdir . '/data.txt'; $data_file = $jobdir . '/data.txt';
$title = ''; $title = '';
@ -135,7 +144,11 @@ if (!is_file($script)) {
$log = $jobdir . '/generate_data.log'; $log = $jobdir . '/generate_data.log';
$bash = '/bin/bash'; $bash = '/bin/bash';
$cmd = escapeshellcmd($bash) . ' ' . escapeshellarg($script) . ' ' . escapeshellarg($jobdir) . ' ' . escapeshellarg($title) . ' ' . escapeshellarg($location) . ' ' . escapeshellarg((string)$max_frames) . ' >> ' . escapeshellarg($log) . ' 2>&1 & echo $!'; if ($additional_prompt !== '') {
$cmd = escapeshellcmd($bash) . ' ' . escapeshellarg($script) . ' ' . escapeshellarg($jobdir) . ' ' . escapeshellarg($title) . ' ' . escapeshellarg($location) . ' ' . escapeshellarg((string)$max_frames) . ' ' . escapeshellarg($additional_prompt) . ' >> ' . escapeshellarg($log) . ' 2>&1 & echo $!';
} else {
$cmd = escapeshellcmd($bash) . ' ' . escapeshellarg($script) . ' ' . escapeshellarg($jobdir) . ' ' . escapeshellarg($title) . ' ' . escapeshellarg($location) . ' ' . escapeshellarg((string)$max_frames) . ' ' . escapeshellarg('no-append') . ' >> ' . escapeshellarg($log) . ' 2>&1 & echo $!';
}
$pid = trim(@shell_exec($cmd)); $pid = trim(@shell_exec($cmd));

View file

@ -10,6 +10,7 @@ header('Content-Type: application/json; charset=utf-8');
$id = strtolower(trim((string)($_POST['id'] ?? ''))); $id = strtolower(trim((string)($_POST['id'] ?? '')));
$job = trim((string)($_POST['job'] ?? '')); $job = trim((string)($_POST['job'] ?? ''));
$max_frames = isset($_POST['max_frames']) ? intval($_POST['max_frames']) : 16; $max_frames = isset($_POST['max_frames']) ? intval($_POST['max_frames']) : 16;
$additional_prompt = trim((string)($_POST['additional_prompt'] ?? ''));
function mvlog_article_id_is_valid(string $id): bool { function mvlog_article_id_is_valid(string $id): bool {
return (bool)preg_match('/^[0-9]{14}[a-f0-9]{16}$/', $id); return (bool)preg_match('/^[0-9]{14}[a-f0-9]{16}$/', $id);
@ -63,6 +64,14 @@ if ($jobdir === null || !is_dir($jobdir)) {
exit; exit;
} }
$describe_prompt_file = $jobdir . '/.mvlog-describe-prompt.txt';
if ($additional_prompt !== '') {
@file_put_contents($describe_prompt_file, $additional_prompt . PHP_EOL);
@chmod($describe_prompt_file, 0664);
@chown($describe_prompt_file, 'www-data');
@chgrp($describe_prompt_file, 'www-data');
}
// Read existing data.txt for Title and Location (case-insensitive aliases) // Read existing data.txt for Title and Location (case-insensitive aliases)
$data_file = $jobdir . '/data.txt'; $data_file = $jobdir . '/data.txt';
$title = ''; $title = '';
@ -101,8 +110,12 @@ if (!is_file($script)) {
exit; exit;
} }
// Build command and execute. Use no-append flag as last argument. // Build command and execute. Pass optional additional prompt as the last argument.
$cmd = escapeshellcmd($script) . ' ' . escapeshellarg($jobdir) . ' ' . escapeshellarg($title) . ' ' . escapeshellarg($location) . ' ' . escapeshellarg((string)$max_frames) . ' ' . escapeshellarg('no-append') . ' 2>&1'; if ($additional_prompt !== '') {
$cmd = escapeshellcmd($script) . ' ' . escapeshellarg($jobdir) . ' ' . escapeshellarg($title) . ' ' . escapeshellarg($location) . ' ' . escapeshellarg((string)$max_frames) . ' ' . escapeshellarg($additional_prompt) . ' 2>&1';
} else {
$cmd = escapeshellcmd($script) . ' ' . escapeshellarg($jobdir) . ' ' . escapeshellarg($title) . ' ' . escapeshellarg($location) . ' ' . escapeshellarg((string)$max_frames) . ' ' . escapeshellarg('no-append') . ' 2>&1';
}
// Allow long-running // Allow long-running
@set_time_limit(0); @set_time_limit(0);