movmaker-webui/_footer_admin.php

299 lines
15 KiB
PHP

<footer class="site-footer admin-footer"><div class="admin-left"><nav class="tabs"><a class="<?= $tab==='new'?'active':'' ?>" href="admin.php?tab=new">New</a><a class="<?= $tab==='edit'?'active':'' ?>" href="admin.php?tab=edit">Edit</a><a class="<?= $tab==='videos'?'active':'' ?>" href="admin.php?tab=videos">Videos</a></nav></div><div id="job-status" class="job-status"<?= empty($runningJobs) ? ' hidden' : '' ?>><?php foreach($runningJobs as $job): ?><span class="job-dot"></span><span><?=h(str_replace('_', ' ', $job['name']))?></span><?php if(!empty($job['started_at'])): ?><span class="job-age">(<?=h(format_job_age($job['started_at']))?>)</span><?php endif; ?><?php endforeach; ?></div><div class="admin-right"><a class="logout-link" href="logout.php">Log off</a></div></footer>
<script>document.querySelectorAll('textarea[data-counter]').forEach(function(t){var c=document.getElementById(t.dataset.counter);function u(){c.textContent=t.value.length+' / '+t.maxLength+' characters';}t.addEventListener('input',u);u();});</script>
<script>
(function(){
function attachDescribeButtons(){
document.querySelectorAll('.admin-video-row').forEach(function(row){
if (row.querySelector('.describe-button')) return;
var actions = row.querySelector('.admin-actions');
if (!actions) return;
var job = row.dataset.job || '';
var articleId = row.dataset.id || '';
if (!job && !articleId) return;
var describe = document.createElement('a');
describe.className = 'button describe-button';
describe.href = '#';
describe.dataset.job = job;
describe.dataset.id = articleId;
describe.textContent = 'Describe';
var first = actions.querySelector('.button');
if (first) actions.insertBefore(describe, first);
else actions.appendChild(describe);
});
}
function setDescribeButtonsDisabled(disabled){
document.querySelectorAll('.describe-button').forEach(function(btn){
if (!btn.dataset.originalText) btn.dataset.originalText = btn.textContent;
btn.classList.toggle('disabled', disabled);
btn.setAttribute('aria-disabled', disabled ? 'true' : 'false');
btn.textContent = disabled ? 'Generating...' : (btn.dataset.originalText || 'Describe');
});
}
function showDescribeBusy(){
setDescribeButtonsDisabled(true);
if (typeof window.showThumbBusy === 'function') {
window.showThumbBusy('Generating description…');
}
}
function hideDescribeBusy(){
if (typeof window.hideThumbBusy === 'function') {
window.hideThumbBusy();
}
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){
var existing = document.getElementById('mvlog-gemini-modal');
if (existing) existing.remove();
var overlay = document.createElement('div');
overlay.id = 'mvlog-gemini-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:900px;max-height:80vh;overflow:auto;border-radius:6px;font-family:inherit;';
var h = document.createElement('h3');
h.textContent = 'Generated description';
var teaserBox = null;
if (teaser) {
teaserBox = document.createElement('div');
teaserBox.style.cssText = 'white-space:pre-wrap;font-family:inherit;margin-top:10px;border:1px solid #eee;padding:10px;background:#fff7e8;border-radius:4px;';
teaserBox.textContent = teaser;
}
var quoteBox = null;
if (quoteDa) {
quoteBox = document.createElement('div');
quoteBox.style.cssText = 'white-space:pre-wrap;font-style:italic;font-family:inherit;margin-top:10px;border:1px solid #eee;padding:10px;background:#f4f0ff;border-radius:4px;';
quoteBox.textContent = '“' + quoteDa + '”';
}
var pre = document.createElement('div');
pre.style.cssText = 'white-space:pre-wrap;font-family:inherit;margin-top:10px;border:1px solid #eee;padding:12px;background:#f8f8f8;border-radius:4px;';
pre.textContent = description || '';
var btnBar = document.createElement('div');
btnBar.style.cssText = 'margin-top:12px;text-align:right;';
var useBtn = document.createElement('button');
useBtn.className = 'button';
useBtn.textContent = 'Use in form';
var cancelBtn = document.createElement('button');
cancelBtn.className = 'button';
cancelBtn.textContent = 'Close';
cancelBtn.style.marginLeft = '8px';
btnBar.appendChild(useBtn);
btnBar.appendChild(cancelBtn);
box.appendChild(h);
if (teaserBox) box.appendChild(teaserBox);
if (quoteBox) box.appendChild(quoteBox);
box.appendChild(pre);
box.appendChild(btnBar);
overlay.appendChild(box);
document.body.appendChild(overlay);
cancelBtn.addEventListener('click', function(){ overlay.remove(); });
useBtn.addEventListener('click', function(){
var editForm = document.getElementById('edit-form');
if (editForm){
var idInput = editForm.querySelector('input[name="id"]');
var dirInput = editForm.querySelector('input[name="dir"]');
var sameById = !!(articleId && idInput && idInput.value === articleId);
var sameByDir = !!(job && dirInput && dirInput.value === job);
if (sameById || sameByDir){
var textarea = editForm.querySelector('textarea[name="description"]');
if (textarea){
textarea.value = description || '';
var teaserInput = editForm.querySelector('input[name="teaser"]');
if (teaserInput && teaser) teaserInput.value = teaser;
var quoteInput = editForm.querySelector('input[name="quote_da"]');
if (quoteInput && quoteDa) quoteInput.value = quoteDa;
showToast('Inserted generated description' + (teaser || quoteDa ? ' and extras' : '') + ' into form. Click Save changes to apply.', true);
overlay.remove();
return;
}
}
}
var storageKey = 'mvlog_gemini_description_' + (articleId || job);
try { localStorage.setItem(storageKey, JSON.stringify({description: description || '', teaser: teaser || '', quote_da: quoteDa || ''})); } catch(e){}
var href = 'admin.php?tab=edit';
if (articleId) href += '&id=' + encodeURIComponent(articleId);
if (job) href += '&edit=' + encodeURIComponent(job);
window.location.href = href;
});
}
document.addEventListener('click', function(e){
var el = (e.target && e.target.closest && e.target.closest('.describe-button')) || (e.target && e.target.classList && e.target.classList.contains && e.target.classList.contains('describe-button') ? e.target : null);
if (!el) return;
e.preventDefault();
var job = el.dataset.job || '';
var articleId = el.dataset.id || '';
if (!job && !articleId) return;
showDescribePromptModal(job, articleId);
});
attachDescribeButtons();
var list = document.querySelector('.admin-list.video-list');
if (list) new MutationObserver(function(){ attachDescribeButtons(); }).observe(list, {childList:true, subtree:true});
// On edit page load: if a generated description is in localStorage, insert it into the form.
(function(){
var editForm = document.getElementById('edit-form');
if (!editForm) return;
var dirInput = editForm.querySelector('input[name="dir"]');
var idInput = editForm.querySelector('input[name="id"]');
if (!dirInput && !idInput) return;
var job = dirInput ? dirInput.value : '';
var articleId = idInput ? idInput.value : '';
try {
var key = 'mvlog_gemini_description_' + (articleId || job);
var raw = localStorage.getItem(key);
if (raw) {
var payload = null;
try { payload = JSON.parse(raw); } catch(e) { payload = {description: raw, teaser: '', quote_da: ''}; }
var desc = (payload && typeof payload === 'object') ? String(payload.description || '') : String(raw || '');
var teaser = (payload && typeof payload === 'object') ? String(payload.teaser || '') : '';
var quoteDa = (payload && typeof payload === 'object') ? String(payload.quote_da || '') : '';
var textarea = editForm.querySelector('textarea[name="description"]');
if (textarea) {
textarea.value = desc;
var teaserInput = editForm.querySelector('input[name="teaser"]');
if (teaserInput && teaser) teaserInput.value = teaser;
var quoteInput = editForm.querySelector('input[name="quote_da"]');
if (quoteInput && quoteDa) quoteInput.value = quoteDa;
showToast('Inserted generated description' + (teaser || quoteDa ? ' and extras' : '') + ' into form. Click Save changes to apply.', true);
}
localStorage.removeItem(key);
}
} catch(e){}
})();
})();
</script>
<script src="js/map_lightbox.js?v=20260531b" defer></script>