mirror of
https://github.com/mwisnowski/mtg_python_deckbuilder.git
synced 2025-12-16 07:30:13 +01:00
604 lines
24 KiB
HTML
604 lines
24 KiB
HTML
{% extends "base.html" %}
|
||
{% from 'partials/_buttons.html' import button %}
|
||
|
||
{% block content %}
|
||
<style>
|
||
/* Setup page-specific styling */
|
||
#content details > summary {
|
||
color: #3b82f6;
|
||
cursor: pointer;
|
||
font-weight: 500;
|
||
}
|
||
#content details > summary:hover {
|
||
color: #60a5fa;
|
||
}
|
||
#content details > div {
|
||
background: #050607 !important;
|
||
border-color: #1e293b !important;
|
||
}
|
||
#content .muted {
|
||
color: #94a3b8;
|
||
}
|
||
/* Make all buttons on this page blue */
|
||
#content button,
|
||
#content input[type="submit"] {
|
||
background: #3b82f6 !important;
|
||
border-color: #3b82f6 !important;
|
||
color: white !important;
|
||
}
|
||
#content button:hover,
|
||
#content input[type="submit"]:hover {
|
||
background: #2563eb !important;
|
||
border-color: #2563eb !important;
|
||
}
|
||
#content button:active,
|
||
#content input[type="submit"]:active {
|
||
background: #1d4ed8 !important;
|
||
}
|
||
/* Progress bars */
|
||
#content [id$="-progress-bar"] {
|
||
background: #0a0c10 !important;
|
||
}
|
||
/* Log output areas */
|
||
#content pre {
|
||
background: #030405 !important;
|
||
border-color: #1e293b !important;
|
||
}
|
||
</style>
|
||
|
||
<section>
|
||
<h2>Setup / Tagging</h2>
|
||
<p class="muted" style="max-width:70ch;">Prepare or refresh the card database and apply tags. You can run this anytime.</p>
|
||
|
||
<details open style="margin-top:.5rem;">
|
||
<summary>Current Status</summary>
|
||
<div id="setup-status" style="margin-top:.5rem; padding:1rem; border:1px solid var(--border); background:#0f1115; border-radius:8px;">
|
||
<div class="muted">Status:</div>
|
||
<div id="setup-status-line" style="margin-top:.25rem;">Checking…</div>
|
||
<div id="setup-progress-line" class="muted" style="margin-top:.25rem; display:none;"></div>
|
||
<div id="setup-progress-bar" style="margin-top:.25rem; width:100%; height:10px; background:#151821; border:1px solid var(--border); border-radius:6px; overflow:hidden; display:none;">
|
||
<div id="setup-progress-bar-inner" style="height:100%; width:0%; background:#3b82f6;"></div>
|
||
</div>
|
||
<div id="setup-time-line" class="muted" style="margin-top:.25rem; display:none;"></div>
|
||
<div id="setup-color-line" class="muted" style="margin-top:.25rem; display:none;"></div>
|
||
<details id="setup-log-wrap" style="margin-top:.5rem; display:none;">
|
||
<summary id="setup-log-summary" class="muted" style="cursor:pointer;">Show logs</summary>
|
||
<pre id="setup-log-tail" style="margin-top:.5rem; max-height:240px; overflow:auto; background:#0b0d12; border:1px solid var(--border); padding:.5rem; border-radius:6px;"></pre>
|
||
</details>
|
||
</div>
|
||
</details>
|
||
|
||
<details style="margin-top:1rem;">
|
||
<summary>Download Pre-tagged Database from GitHub (Optional)</summary>
|
||
<div style="margin-top:.5rem; padding:1rem; border:1px solid var(--border); background:#0f1115; border-radius:8px;">
|
||
<p class="muted" style="margin:0 0 .75rem 0; font-size:.9rem;">
|
||
Download pre-tagged card database and similarity cache from GitHub (updated weekly).
|
||
<strong>Note:</strong> A fresh local tagging run will be most up-to-date with the latest card data.
|
||
</p>
|
||
{{ button('Download from GitHub', variant='priamry', onclick='downloadFromGitHub()', attrs='id="btn-download-github"') }}
|
||
<div id="download-status" class="muted" style="margin-top:.5rem; display:none;"></div>
|
||
</div>
|
||
</details>
|
||
|
||
{% if image_cache_enabled %}
|
||
<details style="margin-top:1rem;">
|
||
<summary>Download Card Images (Optional)</summary>
|
||
<div style="margin-top:.5rem; padding:1rem; border:1px solid var(--border); background:#0f1115; border-radius:8px;">
|
||
<p class="muted" style="margin:0 0 .75rem 0; font-size:.9rem;">
|
||
Download card images from Scryfall CDN for faster loading and offline use.
|
||
<strong>Note:</strong> Requires ~3-6 GB disk space and 1-2 hours download time (~30k cards).
|
||
</p>
|
||
<div id="image-cache-status" style="margin-bottom:.75rem;">
|
||
<div class="muted">Status:</div>
|
||
<div id="image-status-line" style="margin-top:.25rem;">Checking…</div>
|
||
<div class="muted" id="image-stats-line" style="margin-top:.25rem; display:none;"></div>
|
||
</div>
|
||
{{ button('Download Card Images', variant='priamry', onclick='downloadCardImages()', attrs='id="btn-download-images"') }}
|
||
<div id="image-download-status" class="muted" style="margin-top:.5rem; display:none;"></div>
|
||
<div id="image-progress-bar" style="margin-top:.5rem; width:100%; height:10px; background:#151821; border:1px solid var(--border); border-radius:6px; overflow:hidden; display:none;">
|
||
<div id="image-progress-bar-inner" style="height:100%; width:0%; background:#3b82f6;"></div>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
{% endif %}
|
||
|
||
<div style="margin-top:1rem; display:flex; gap:.5rem; flex-wrap:wrap;">
|
||
<form id="frm-start-setup" action="/setup/start" method="post" onsubmit="event.preventDefault(); startSetup();">
|
||
{{ button('Run Setup/Tagging', variant='primary', type='submit', attrs='id="btn-start-setup"') }}
|
||
<label class="muted" style="margin-left:.75rem; font-size:.9rem;">
|
||
<input type="checkbox" id="chk-force" checked /> Force run
|
||
</label>
|
||
</form>
|
||
<form method="get" action="/setup/running?start=1&force=1">
|
||
{{ button('Open Progress Page', variant='primary', type='submit') }}
|
||
</form>
|
||
</div>
|
||
|
||
<details style="margin-top:1.25rem;" open>
|
||
<summary>Theme Catalog Status</summary>
|
||
<div id="themes-status" style="margin-top:.5rem; padding:1rem; border:1px solid var(--border); background:#0f1115; border-radius:8px;">
|
||
<div class="muted">Status:</div>
|
||
<div id="themes-status-line" style="margin-top:.25rem;">Checking…</div>
|
||
<div class="muted" id="themes-meta-line" style="margin-top:.25rem; display:none;"></div>
|
||
<div class="muted" id="themes-stale-line" style="margin-top:.25rem; display:none; color:#f87171;"></div>
|
||
</div>
|
||
</details>
|
||
<div style="margin-top:.75rem; display:flex; gap:.5rem; flex-wrap:wrap;">
|
||
{{ button('Refresh Themes Only', variant='priamry', onclick='refreshThemes()', attrs='id="btn-refresh-themes"') }}
|
||
</div>
|
||
|
||
{% if similarity_enabled %}
|
||
<details style="margin-top:1.25rem;" open>
|
||
<summary>Similarity Cache Status</summary>
|
||
<div id="similarity-status" style="margin-top:.5rem; padding:1rem; border:1px solid var(--border); background:#0f1115; border-radius:8px;">
|
||
<div class="muted">Status:</div>
|
||
<div id="similarity-status-line" style="margin-top:.25rem;">Checking…</div>
|
||
<div class="muted" id="similarity-meta-line" style="margin-top:.25rem; display:none;"></div>
|
||
<div class="muted" id="similarity-warning-line" style="margin-top:.25rem; display:none; color:#f59e0b;"></div>
|
||
</div>
|
||
</details>
|
||
<div style="margin-top:.75rem; display:flex; gap:.5rem; flex-wrap:wrap;">
|
||
{{ button('Build Similarity Cache', variant='priamry', onclick='buildSimilarityCache()', attrs='id="btn-build-similarity"') }}
|
||
<label class="muted" style="align-self:center; font-size:.85rem;">
|
||
<input type="checkbox" id="chk-skip-download" /> Skip GitHub download (build locally)
|
||
</label>
|
||
<span class="muted" style="align-self:center; font-size:.85rem;">(~15-20 min local, instant if cached on GitHub)</span>
|
||
</div>
|
||
{% endif %}
|
||
</section>
|
||
<script>
|
||
(function(){
|
||
// Minimal styling helper to unify button widths
|
||
try {
|
||
var style = document.createElement('style');
|
||
style.textContent = '.btn{min-width:180px;}';
|
||
document.head.appendChild(style);
|
||
} catch(e){}
|
||
function update(data){
|
||
var line = document.getElementById('setup-status-line');
|
||
var colorEl = document.getElementById('setup-color-line');
|
||
var logEl = document.getElementById('setup-log-tail');
|
||
var progEl = document.getElementById('setup-progress-line');
|
||
var timeEl = document.getElementById('setup-time-line');
|
||
var bar = document.getElementById('setup-progress-bar');
|
||
var barIn = document.getElementById('setup-progress-bar-inner');
|
||
var logWrap = document.getElementById('setup-log-wrap');
|
||
var logSummary = document.getElementById('setup-log-summary');
|
||
if (!line) return;
|
||
if (data && data.running) {
|
||
line.textContent = (data.message || 'Working…');
|
||
if (typeof data.percent === 'number') {
|
||
progEl.style.display = '';
|
||
var p = Math.max(0, Math.min(100, data.percent));
|
||
progEl.textContent = 'Progress: ' + p + '%';
|
||
if (bar && barIn) { bar.style.display = ''; barIn.style.width = p + '%'; }
|
||
if (typeof data.color_idx === 'number' && typeof data.color_total === 'number') {
|
||
progEl.textContent += ' • Colors: ' + data.color_idx + ' / ' + data.color_total;
|
||
}
|
||
if (typeof data.eta_seconds === 'number') {
|
||
var mins = Math.floor(data.eta_seconds / 60); var secs = data.eta_seconds % 60;
|
||
progEl.textContent += ' • ETA: ~' + mins + 'm ' + secs + 's';
|
||
}
|
||
} else {
|
||
progEl.style.display = 'none';
|
||
if (bar) bar.style.display = 'none';
|
||
}
|
||
if (data.started_at) {
|
||
timeEl.style.display = '';
|
||
timeEl.textContent = 'Started: ' + data.started_at;
|
||
} else {
|
||
timeEl.style.display = 'none';
|
||
}
|
||
if (data.color) {
|
||
colorEl.style.display = '';
|
||
colorEl.textContent = 'Current color: ' + data.color;
|
||
} else {
|
||
colorEl.style.display = 'none';
|
||
}
|
||
if (data.log_tail) {
|
||
var lines = data.log_tail.split(/\r?\n/).filter(function(x){ return x.trim() !== ''; });
|
||
if (logWrap) logWrap.style.display = '';
|
||
if (logSummary) logSummary.textContent = 'Show logs (' + lines.length + ' lines)';
|
||
logEl.textContent = data.log_tail;
|
||
} else {
|
||
if (logWrap) logWrap.style.display = 'none';
|
||
}
|
||
} else if (data && data.phase === 'done') {
|
||
line.textContent = 'Setup complete.';
|
||
if (typeof data.percent === 'number') {
|
||
progEl.style.display = '';
|
||
var p2 = Math.max(0, Math.min(100, data.percent));
|
||
progEl.textContent = 'Progress: ' + p2 + '%';
|
||
if (bar && barIn) { bar.style.display = ''; barIn.style.width = p2 + '%'; }
|
||
} else {
|
||
progEl.style.display = 'none';
|
||
if (bar) bar.style.display = 'none';
|
||
}
|
||
if (data.started_at || data.finished_at) {
|
||
timeEl.style.display = '';
|
||
var t = [];
|
||
if (data.started_at) t.push('Started: ' + data.started_at);
|
||
if (data.finished_at) t.push('Finished: ' + data.finished_at);
|
||
timeEl.textContent = t.join(' • ');
|
||
} else {
|
||
timeEl.style.display = 'none';
|
||
}
|
||
colorEl.style.display = 'none';
|
||
if (logWrap) logWrap.style.display = 'none';
|
||
} else if (data && data.phase === 'error') {
|
||
line.textContent = (data.message || 'Setup error.');
|
||
if (data.color) {
|
||
colorEl.style.display = '';
|
||
colorEl.textContent = 'While working on: ' + data.color;
|
||
}
|
||
} else {
|
||
line.textContent = 'Idle';
|
||
progEl.style.display = 'none';
|
||
timeEl.style.display = 'none';
|
||
colorEl.style.display = 'none';
|
||
if (logWrap) logWrap.style.display = 'none';
|
||
}
|
||
}
|
||
function poll(){
|
||
fetch('/status/setup', { cache: 'no-store' })
|
||
.then(function(r){ return r.json(); })
|
||
.then(update)
|
||
.catch(function(){});
|
||
pollThemes();
|
||
}
|
||
function pollThemes(){
|
||
fetch('/themes/status', { cache: 'no-store' })
|
||
.then(function(r){ return r.json(); })
|
||
.then(updateThemes)
|
||
.catch(function(){});
|
||
}
|
||
function updateThemes(data){
|
||
var line = document.getElementById('themes-status-line');
|
||
var meta = document.getElementById('themes-meta-line');
|
||
var staleEl = document.getElementById('themes-stale-line');
|
||
var btn = document.getElementById('btn-refresh-themes');
|
||
if(!line) return;
|
||
if(!data || !data.ok){ line.textContent = 'Unavailable'; return; }
|
||
var parts = [];
|
||
if (typeof data.theme_count === 'number') parts.push('Themes: '+data.theme_count);
|
||
if (typeof data.yaml_file_count === 'number') parts.push('YAML: '+data.yaml_file_count);
|
||
if (data.last_export_at) parts.push('Last Export: '+data.last_export_at);
|
||
line.textContent = (data.theme_list_exists ? 'Ready' : 'Not generated');
|
||
if(parts.length){ meta.style.display=''; meta.textContent = parts.join(' • '); } else { meta.style.display='none'; }
|
||
if(data.stale){ staleEl.style.display=''; staleEl.textContent='Stale: needs refresh'; }
|
||
else { staleEl.style.display='none'; }
|
||
// Disable refresh while a theme export phase is active (in orchestrator phases 'themes' / 'themes-fast')
|
||
try {
|
||
if(btn){
|
||
if(data.phase === 'themes' || data.phase === 'themes-fast'){
|
||
btn.disabled = true; btn.textContent='Refreshing…';
|
||
} else {
|
||
if(!data.running){ btn.disabled = false; btn.textContent='Refresh Themes Only'; }
|
||
}
|
||
}
|
||
} catch(e){}
|
||
}
|
||
window.refreshThemes = function(){
|
||
var btn = document.getElementById('btn-refresh-themes');
|
||
if(btn) btn.disabled = true;
|
||
fetch('/themes/refresh', { method:'POST' })
|
||
.then(function(){ setTimeout(function(){ pollThemes(); if(btn) btn.disabled=false; }, 1200); })
|
||
.catch(function(){ if(btn) btn.disabled=false; });
|
||
};
|
||
|
||
// Card image download functions
|
||
function pollImageStatus(){
|
||
fetch('/api/images/status', { cache: 'no-store' })
|
||
.then(function(r){ return r.json(); })
|
||
.then(function(data){
|
||
var statusLine = document.getElementById('image-status-line');
|
||
var statsLine = document.getElementById('image-stats-line');
|
||
var downloadStatus = document.getElementById('image-download-status');
|
||
var progressBar = document.getElementById('image-progress-bar');
|
||
var progressBarInner = document.getElementById('image-progress-bar-inner');
|
||
|
||
if (!statusLine) return;
|
||
|
||
if (data.running) {
|
||
// Download in progress
|
||
var phase = data.phase || 'unknown';
|
||
var message = data.message || 'Downloading...';
|
||
var percentage = data.percentage || 0;
|
||
|
||
statusLine.textContent = 'Download in progress';
|
||
statusLine.style.color = '#3b82f6';
|
||
|
||
if (downloadStatus) {
|
||
downloadStatus.style.display = '';
|
||
downloadStatus.textContent = message + ' (' + percentage + '%)';
|
||
}
|
||
|
||
if (progressBar && progressBarInner) {
|
||
progressBar.style.display = '';
|
||
progressBarInner.style.width = percentage + '%';
|
||
}
|
||
} else if (data.stats) {
|
||
// Show cache statistics
|
||
var stats = data.stats;
|
||
if (stats.enabled === false) {
|
||
statusLine.textContent = 'Image caching disabled';
|
||
statusLine.style.color = '#94a3b8';
|
||
if (statsLine) statsLine.style.display = 'none';
|
||
} else {
|
||
var totalCount = 0;
|
||
var totalSizeMB = 0;
|
||
|
||
if (stats.small) {
|
||
totalCount += stats.small.count || 0;
|
||
totalSizeMB += stats.small.size_mb || 0;
|
||
}
|
||
if (stats.normal) {
|
||
totalCount += stats.normal.count || 0;
|
||
totalSizeMB += stats.normal.size_mb || 0;
|
||
}
|
||
|
||
if (totalCount > 0) {
|
||
statusLine.textContent = 'Cache exists';
|
||
statusLine.style.color = '#34d399';
|
||
if (statsLine) {
|
||
statsLine.style.display = '';
|
||
statsLine.textContent = totalCount.toLocaleString() + ' images cached • ' + totalSizeMB.toFixed(1) + ' MB';
|
||
}
|
||
} else {
|
||
statusLine.textContent = 'No images cached';
|
||
statusLine.style.color = '#94a3b8';
|
||
if (statsLine) statsLine.style.display = 'none';
|
||
}
|
||
}
|
||
|
||
// Hide download progress
|
||
if (downloadStatus) downloadStatus.style.display = 'none';
|
||
if (progressBar) progressBar.style.display = 'none';
|
||
}
|
||
})
|
||
.catch(function(){
|
||
var statusLine = document.getElementById('image-status-line');
|
||
if (statusLine) {
|
||
statusLine.textContent = 'Status unavailable';
|
||
statusLine.style.color = '#94a3b8';
|
||
}
|
||
});
|
||
}
|
||
|
||
window.downloadCardImages = function(){
|
||
var btn = document.getElementById('btn-download-images');
|
||
var statusEl = document.getElementById('image-download-status');
|
||
|
||
if (!confirm('Download card images from Scryfall? This will download ~30k images (~3-6 GB) and may take 1-2 hours.')) {
|
||
return;
|
||
}
|
||
|
||
if (btn) btn.disabled = true;
|
||
if (statusEl) {
|
||
statusEl.style.display = '';
|
||
statusEl.textContent = 'Starting download...';
|
||
}
|
||
|
||
fetch('/api/images/download', { method: 'POST' })
|
||
.then(function(r){
|
||
if (!r.ok) {
|
||
return r.json().then(function(data){
|
||
throw new Error(data.message || 'Download failed');
|
||
});
|
||
}
|
||
return r.json();
|
||
})
|
||
.then(function(data){
|
||
if (statusEl) {
|
||
statusEl.style.color = '#34d399';
|
||
statusEl.textContent = '✓ ' + (data.message || 'Download started');
|
||
}
|
||
|
||
// Poll status every 2 seconds while downloading
|
||
var pollCount = 0;
|
||
var imagePoll = setInterval(function(){
|
||
pollImageStatus();
|
||
pollCount++;
|
||
// Stop intensive polling after 2 hours (3600 polls)
|
||
if (pollCount > 3600) clearInterval(imagePoll);
|
||
}, 2000);
|
||
|
||
setTimeout(function(){
|
||
if (btn) btn.disabled = false;
|
||
}, 3000);
|
||
})
|
||
.catch(function(err){
|
||
if (statusEl) {
|
||
statusEl.style.color = '#f87171';
|
||
statusEl.textContent = '✗ ' + (err.message || 'Download failed');
|
||
}
|
||
if (btn) btn.disabled = false;
|
||
});
|
||
};
|
||
|
||
function rapidPoll(times, delay){
|
||
var i = 0;
|
||
function tick(){
|
||
poll();
|
||
i++;
|
||
if (i < times) setTimeout(tick, delay);
|
||
}
|
||
tick();
|
||
}
|
||
window.downloadFromGitHub = function(){
|
||
var btn = document.getElementById('btn-download-github');
|
||
var statusEl = document.getElementById('download-status');
|
||
if (btn) btn.disabled = true;
|
||
if (statusEl) {
|
||
statusEl.style.display = '';
|
||
statusEl.textContent = 'Downloading from GitHub...';
|
||
}
|
||
|
||
fetch('/setup/download-github', { method: 'POST' })
|
||
.then(function(r){
|
||
if (!r.ok) throw new Error('Download failed');
|
||
return r.json();
|
||
})
|
||
.then(function(data){
|
||
if (statusEl) {
|
||
statusEl.style.color = '#34d399';
|
||
statusEl.textContent = '✓ ' + (data.message || 'Download complete');
|
||
}
|
||
// Refresh status displays
|
||
poll();
|
||
setTimeout(function(){ if (btn) btn.disabled = false; }, 2000);
|
||
})
|
||
.catch(function(err){
|
||
if (statusEl) {
|
||
statusEl.style.color = '#f87171';
|
||
statusEl.textContent = '✗ Download failed: ' + (err.message || 'Unknown error');
|
||
}
|
||
if (btn) btn.disabled = false;
|
||
});
|
||
};
|
||
window.startSetup = function(){
|
||
var btn = document.getElementById('btn-start-setup');
|
||
var line = document.getElementById('setup-status-line');
|
||
var force = document.getElementById('chk-force') && document.getElementById('chk-force').checked;
|
||
if (btn) btn.disabled = true;
|
||
if (line) line.textContent = 'Starting setup/tagging…';
|
||
// First try POST with JSON body
|
||
fetch('/setup/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ force: !!force }) })
|
||
.then(function(r){ if (!r.ok) throw new Error('POST failed'); return r.json().catch(function(){ return {}; }); })
|
||
.then(function(){ rapidPoll(5, 600); setTimeout(function(){ window.location.href = '/setup/running?start=1' + (force ? '&force=1' : ''); }, 500); })
|
||
.catch(function(){
|
||
// Fallback to GET if POST fails (proxy/middleware issues)
|
||
var url = '/setup/start' + (force ? '?force=1' : '');
|
||
fetch(url, { method: 'GET', cache: 'no-store' })
|
||
.then(function(){ rapidPoll(5, 600); setTimeout(function(){ window.location.href = '/setup/running?start=1' + (force ? '&force=1' : ''); }, 500); })
|
||
.catch(function(){});
|
||
})
|
||
.finally(function(){ if (btn) btn.disabled = false; });
|
||
};
|
||
|
||
// Similarity cache status polling
|
||
{% if similarity_enabled %}
|
||
function pollSimilarityStatus(){
|
||
fetch('/status/similarity', { cache: 'no-store' })
|
||
.then(function(r){ return r.json(); })
|
||
.then(function(data){
|
||
var line = document.getElementById('similarity-status-line');
|
||
var metaLine = document.getElementById('similarity-meta-line');
|
||
var warnLine = document.getElementById('similarity-warning-line');
|
||
if (!line) return;
|
||
|
||
if (data.exists && data.valid) {
|
||
var cardCount = data.card_count ? data.card_count.toLocaleString() : '?';
|
||
var sizeMB = data.size_mb ? data.size_mb.toFixed(1) : '?';
|
||
var ageDays = data.age_days !== null ? data.age_days.toFixed(1) : '?';
|
||
|
||
line.textContent = 'Cache exists and is valid';
|
||
line.style.color = '#34d399';
|
||
|
||
if (metaLine) {
|
||
metaLine.style.display = '';
|
||
metaLine.textContent = cardCount + ' cards cached • ' + sizeMB + ' MB • ' + ageDays + ' days old';
|
||
}
|
||
|
||
if (warnLine && data.needs_refresh) {
|
||
warnLine.style.display = '';
|
||
warnLine.textContent = '⚠ Cache is ' + ageDays + ' days old. Consider rebuilding for fresher data.';
|
||
} else if (warnLine) {
|
||
warnLine.style.display = 'none';
|
||
}
|
||
} else if (data.exists && !data.valid) {
|
||
line.textContent = 'Cache file is invalid or corrupted';
|
||
line.style.color = '#f87171';
|
||
if (metaLine) metaLine.style.display = 'none';
|
||
if (warnLine) {
|
||
warnLine.style.display = '';
|
||
warnLine.textContent = '⚠ Rebuild cache to fix.';
|
||
}
|
||
} else {
|
||
line.textContent = 'No cache found';
|
||
line.style.color = '#94a3b8';
|
||
if (metaLine) metaLine.style.display = 'none';
|
||
if (warnLine) {
|
||
warnLine.style.display = '';
|
||
warnLine.textContent = 'ℹ Build cache to enable similar card features.';
|
||
}
|
||
}
|
||
})
|
||
.catch(function(){});
|
||
}
|
||
|
||
window.buildSimilarityCache = function(){
|
||
var btn = document.getElementById('btn-build-similarity');
|
||
var skipDownloadCheckbox = document.getElementById('chk-skip-download');
|
||
if (!btn) return;
|
||
|
||
var skipDownload = skipDownloadCheckbox && skipDownloadCheckbox.checked;
|
||
var confirmMsg = skipDownload
|
||
? 'Build similarity cache locally for ~30k cards? This will take approximately 15-20 minutes and uses parallel processing.'
|
||
: 'Build similarity cache? This will first try to download a pre-built cache from GitHub (instant), or build locally if unavailable (~15-20 minutes).';
|
||
|
||
if (!confirm(confirmMsg)) {
|
||
return;
|
||
}
|
||
|
||
btn.disabled = true;
|
||
btn.textContent = 'Building... (check terminal for progress)';
|
||
|
||
var body = skipDownload ? JSON.stringify({ skip_download: true }) : '{}';
|
||
|
||
fetch('/similarity/build', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: body
|
||
})
|
||
.then(function(r){
|
||
if (!r.ok) throw new Error('Build failed');
|
||
return r.json();
|
||
})
|
||
.then(function(data){
|
||
if (data.success) {
|
||
btn.textContent = 'Build Started! Check terminal for progress...';
|
||
// Poll status more frequently while building
|
||
var pollCount = 0;
|
||
var buildPoll = setInterval(function(){
|
||
pollSimilarityStatus();
|
||
pollCount++;
|
||
// Stop intensive polling after 2 minutes, rely on normal polling
|
||
if (pollCount > 40) clearInterval(buildPoll);
|
||
}, 3000);
|
||
|
||
setTimeout(function(){
|
||
btn.textContent = 'Build Similarity Cache';
|
||
btn.disabled = false;
|
||
}, 8000);
|
||
} else {
|
||
btn.textContent = 'Build Failed: ' + (data.error || 'Unknown error');
|
||
setTimeout(function(){
|
||
btn.textContent = 'Build Similarity Cache';
|
||
btn.disabled = false;
|
||
}, 3000);
|
||
}
|
||
})
|
||
.catch(function(err){
|
||
btn.textContent = 'Build Failed';
|
||
setTimeout(function(){
|
||
btn.textContent = 'Build Similarity Cache';
|
||
btn.disabled = false;
|
||
}, 3000);
|
||
});
|
||
};
|
||
|
||
pollSimilarityStatus();
|
||
setInterval(pollSimilarityStatus, 10000); // Poll every 10s
|
||
{% endif %}
|
||
|
||
// Initialize image status polling
|
||
pollImageStatus();
|
||
setInterval(pollImageStatus, 10000); // Poll every 10s
|
||
|
||
setInterval(poll, 3000);
|
||
poll();
|
||
pollThemes();
|
||
})();
|
||
</script>
|
||
{% endblock %}
|