• Implemented global offline mode for all HuggingFace-dependent modules to eliminate unauthenticated request warnings and prevent external connection attempts during startup and runtime. Changes • Global Offline Configuration: Added HF_HUB_OFFLINE=1 and TRANSFORMERS_OFFLINE=1 to the environment variables at the top of edit_api.py, embeddings.py, watcher.py, and pose_llm/pose_llm_api.py. • Explicit Local Files Only: Updated all huggingface_hub.hf_hub_download calls in edit_api.py to include local_files_only=True, ensuring they never attempt to reach the Hub if models are already cached. • LLM Service Optimization: Updated AutoTokenizer and AutoModelForCausalLM calls in pose_llm/pose_llm_api.py to use local_files_only=True. • Consistency: Ensured that shared modules like embeddings.py which uses open_clip are also locked to offline mode to prevent background downloads.
247 lines
10 KiB
HTML
247 lines
10 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>Qwen Rapid-AIO — Database Consistency</title>
|
|
<style>
|
|
body {
|
|
background: #111;
|
|
color: #ccc;
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
margin: 0;
|
|
padding: 20px;
|
|
}
|
|
h1, h2 { color: #eee; }
|
|
.container { max-width: 1000px; margin: 0 auto; }
|
|
.section {
|
|
background: #1a1a1a;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
margin-bottom: 20px;
|
|
border: 1px solid #333;
|
|
}
|
|
.item {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 8px;
|
|
border-bottom: 1px solid #222;
|
|
}
|
|
.item:last-child { border-bottom: none; }
|
|
.filename { font-family: monospace; color: #aaa; }
|
|
.info { font-size: 0.9em; color: #777; }
|
|
.btn {
|
|
background: #333;
|
|
color: #eee;
|
|
border: none;
|
|
padding: 6px 12px;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 0.9em;
|
|
}
|
|
.btn:hover { background: #444; }
|
|
.btn.danger { background: #633; }
|
|
.btn.danger:hover { background: #844; }
|
|
.btn.success { background: #363; }
|
|
.btn.success:hover { background: #484; }
|
|
.btn.primary { background: #35a; font-weight: bold; }
|
|
.btn.primary:hover { background: #46b; }
|
|
.header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 20px; }
|
|
.nav { margin-bottom: 20px; }
|
|
.nav a { color: #58a; text-decoration: none; font-size: 0.9em; }
|
|
.nav a:hover { text-decoration: underline; }
|
|
#toast {
|
|
position: fixed;
|
|
bottom: 20px;
|
|
right: 20px;
|
|
background: #333;
|
|
color: #fff;
|
|
padding: 10px 20px;
|
|
border-radius: 4px;
|
|
display: none;
|
|
z-index: 1000;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="nav">
|
|
<a href="car.html">← Back to Studio</a>
|
|
</div>
|
|
<div class="header">
|
|
<h1>Database Consistency</h1>
|
|
<button class="btn primary" onclick="loadInconsistencies(true)">Run Check Now</button>
|
|
</div>
|
|
|
|
<div id="loading" style="display:none">Running consistency check...</div>
|
|
|
|
<div class="section">
|
|
<h2>Archived Items <span class="info">(Manually archived, recoverable)</span></h2>
|
|
<div id="archivedList">No archived items found.</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>Missing Group IDs <span class="info">(Legacy or orphaned images with no group assigned)</span></h2>
|
|
<div id="orphansList">No images with missing groups found.</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>Orphaned Records <span class="info">(In DB, but file missing on disk)</span></h2>
|
|
<div id="missingList">No orphaned records found.</div>
|
|
</div>
|
|
|
|
<div class="section">
|
|
<h2>Untracked Files <span class="info">(On disk, but not in DB)</span></h2>
|
|
<div id="untrackedList">No untracked files found.</div>
|
|
</div>
|
|
|
|
<div class="info" id="timestamp"></div>
|
|
</div>
|
|
|
|
<div id="toast"></div>
|
|
|
|
<script>
|
|
let API = window.location.origin;
|
|
if (API === 'null' || API.startsWith('file://')) {
|
|
API = 'http://127.0.0.1:8500';
|
|
}
|
|
|
|
function showToast(msg) {
|
|
const t = document.getElementById('toast');
|
|
t.textContent = msg;
|
|
t.style.display = 'block';
|
|
setTimeout(() => t.style.display = 'none', 3000);
|
|
}
|
|
|
|
async function repair(filename, action) {
|
|
try {
|
|
const r = await fetch(`${API}/db/repair`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ filename, action })
|
|
});
|
|
const d = await r.json();
|
|
let msg = 'Done';
|
|
if (d.status === 'deleted') msg = 'Record deleted';
|
|
else if (d.status === 'imported') msg = 'File imported';
|
|
else if (d.status === 'restored') msg = 'Item restored';
|
|
else if (d.status === 'deleted_permanently') msg = 'Permanently deleted';
|
|
else if (d.status === 'assigned') msg = 'Group assigned';
|
|
|
|
showToast(msg);
|
|
loadInconsistencies();
|
|
} catch (e) {
|
|
console.error(e);
|
|
showToast('Action failed');
|
|
}
|
|
}
|
|
|
|
async function loadInconsistencies(runNow = false) {
|
|
if (runNow) document.getElementById('loading').style.display = 'block';
|
|
try {
|
|
const url = runNow ? `${API}/db/inconsistencies?run_now=true` : `${API}/db/inconsistencies`;
|
|
const r = await fetch(url);
|
|
const d = await r.json();
|
|
|
|
renderArchived(d.archived_items || []);
|
|
renderMissing(d.missing_files || []);
|
|
renderUntracked(d.untracked_files || []);
|
|
renderOrphans(d.missing_group || []);
|
|
|
|
if (d.timestamp) {
|
|
const date = new Date(d.timestamp * 1000);
|
|
document.getElementById('timestamp').textContent = 'Last check: ' + date.toLocaleString();
|
|
}
|
|
} catch (e) {
|
|
console.error(e);
|
|
showToast('Failed to load inconsistencies');
|
|
} finally {
|
|
document.getElementById('loading').style.display = 'none';
|
|
}
|
|
}
|
|
|
|
function renderOrphans(files) {
|
|
const container = document.getElementById('orphansList');
|
|
if (files.length === 0) {
|
|
container.innerHTML = 'No images with missing groups found.';
|
|
return;
|
|
}
|
|
container.innerHTML = files.map(f => `
|
|
<div class="item">
|
|
<div style="display:flex;align-items:center">
|
|
<img src="${API}/output/${encodeURIComponent(f.filename)}" style="height:50px;width:50px;object-fit:cover;border-radius:4px;margin-right:12px;background:#222;border:1px solid #333" onerror="this.style.display='none'">
|
|
<div>
|
|
<div class="filename">${f.filename}</div>
|
|
<div class="info">Name: ${f.name || 'none'}</div>
|
|
</div>
|
|
</div>
|
|
<button class="btn success" onclick="repair('${f.filename}', 'assign_group')">Auto-assign Group</button>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
function renderArchived(files) {
|
|
const container = document.getElementById('archivedList');
|
|
if (files.length === 0) {
|
|
container.innerHTML = 'No archived items found.';
|
|
return;
|
|
}
|
|
container.innerHTML = files.map(f => `
|
|
<div class="item">
|
|
<div style="display:flex;align-items:center">
|
|
<img src="${API}/output/${encodeURIComponent(f.filename)}" style="height:50px;width:50px;object-fit:cover;border-radius:4px;margin-right:12px;background:#222;border:1px solid #333" onerror="this.style.display='none'">
|
|
<div>
|
|
<div class="filename">${f.filename}</div>
|
|
<div class="info">Group: ${f.group_id || 'none'} | Name: ${f.name || 'none'}</div>
|
|
</div>
|
|
</div>
|
|
<div style="display:flex;gap:8px">
|
|
<button class="btn success" onclick="repair('${f.filename}', 'restore')">Restore</button>
|
|
<button class="btn danger" onclick="if(confirm('Permanently delete ${f.filename}?')) repair('${f.filename}', 'delete_permanently')">Delete</button>
|
|
</div>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
function renderMissing(files) {
|
|
const container = document.getElementById('missingList');
|
|
if (files.length === 0) {
|
|
container.innerHTML = 'No orphaned records found.';
|
|
return;
|
|
}
|
|
container.innerHTML = files.map(f => `
|
|
<div class="item">
|
|
<div style="display:flex;align-items:center">
|
|
<img src="${API}/output/${encodeURIComponent(f.filename)}" style="height:50px;width:50px;object-fit:cover;border-radius:4px;margin-right:12px;background:#222;border:1px solid #333" onerror="this.style.display='none'">
|
|
<div>
|
|
<div class="filename">${f.filename}</div>
|
|
<div class="info">Group: ${f.group_id || 'none'} | Name: ${f.name || 'none'}</div>
|
|
</div>
|
|
</div>
|
|
<button class="btn danger" onclick="repair('${f.filename}', 'delete_record')">Delete Record</button>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
function renderUntracked(files) {
|
|
const container = document.getElementById('untrackedList');
|
|
if (files.length === 0) {
|
|
container.innerHTML = 'No untracked files found.';
|
|
return;
|
|
}
|
|
container.innerHTML = files.map(f => `
|
|
<div class="item">
|
|
<div style="display:flex;align-items:center">
|
|
<img src="${API}/output/${encodeURIComponent(f)}" style="height:50px;width:50px;object-fit:cover;border-radius:4px;margin-right:12px;background:#222;border:1px solid #333" onerror="this.style.display='none'">
|
|
<div class="filename">${f}</div>
|
|
</div>
|
|
<button class="btn success" onclick="repair('${f}', 'import_file')">Import File</button>
|
|
</div>
|
|
`).join('');
|
|
}
|
|
|
|
loadInconsistencies();
|
|
</script>
|
|
</body>
|
|
</html>
|