updates UI

This commit is contained in:
mike
2026-07-01 05:42:20 +02:00
parent 251d9b1cc8
commit 94419d7673
5 changed files with 298 additions and 95 deletions

View File

@@ -97,6 +97,10 @@ Scenery:
- in order to decouple the landingpage/dashboard from the studio view (editor) we are going to generate group "shoot" specific html pages. Start by just generate the studio portion of car.html into the group specifc html pages. It does not need to be synchronous, but when data of the group is changing we should update the group specifc data json file too, we copy the goup files to the output folder next to car.html
- in /mnt/zim/tour-comfy/output/_turntable we have data regarding orbit generations. We want to store the orbit data in the database, start by adding the corresponding tags and meta info to the images (currently stored in the person table (but actually are images with metadata))
- in the ui - studio view we have an orbit filter. Currently the filter is filtering too many files, also references of orbit files are being marked orbit. When an orbit file is used, the metadata should not include the orbit tag.
- pose-prompt has at least three parts, [scenery ,pose, art-form] allow in the ui to compound the prompt with these, scene default = "You are in a black void" art-form= "photo-realistic keep identical person from reference" also allow inherit scenery to as option.
## refine
#file:///mnt/zim/tour-comfy/output/shoot_cg_7ec17537.html
- when refresh page, we lose track of current jobs running.

View File

@@ -1330,10 +1330,20 @@
inset: 0;
background: #0a0a0a;
z-index: 500;
flex-direction: column;
flex-direction: row; /* row layout to support full height sidebar */
overflow: hidden;
}
.studio.open { display: flex; }
.studio-left {
display: flex;
flex-direction: column;
flex: 1;
min-width: 0;
height: 100%;
overflow: hidden;
}
.studio-topbar {
flex-shrink: 0;
display: flex;
@@ -1348,10 +1358,6 @@
.studio-topbar-center{ flex: 1; min-width: 0; display: flex; align-items: center; gap: 10px; overflow: hidden; }
.studio-topbar-right { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.studio-body {
flex: 1; min-height: 0;
display: flex; overflow: hidden;
}
.studio-main {
flex: 1; min-width: 0;
display: flex; flex-direction: column; overflow: hidden;
@@ -1464,7 +1470,7 @@
.sidebar-inner {
flex: 1; min-height: 0;
display: flex; flex-direction: column;
padding-top: 36px; overflow: hidden;
padding-top: 8px; overflow: hidden;
transition: opacity 0.15s;
}
.studio-sidebar.collapsed .sidebar-inner { opacity: 0; pointer-events: none; }
@@ -1936,17 +1942,6 @@
</style>
</head>
<body>
<!-- Floating services status container -->
<div class="services-status-container" style="position: fixed; bottom: 12px; left: 12px; z-index: 11000; display: flex; align-items: center; gap: 14px; background: rgba(18, 18, 18, 0.9); border: 1px solid #2e2e2e; padding: 6px 12px; border-radius: 20px; font-family: monospace; font-size: 11px; color: #aaa; box-shadow: 0 4px 12px rgba(0,0,0,0.5); backdrop-filter: blur(4px);">
<div style="display: flex; align-items: center; gap: 6px; cursor: help;" id="backendStatus" title="Backend: Offline (API server is down)">
<span class="status-dot-led offline" id="backendDot"></span>
<span>API</span>
</div>
<div style="display: flex; align-items: center; gap: 6px; cursor: help;" id="modelStatus" title="Model-Engine: Status unknown (API server is down)">
<span class="status-dot-led offline" id="modelDot"></span>
<span>Model</span>
</div>
</div>
<div id="privacyOverlay" class="privacy-overlay" onclick="if(privacyMode)disablePrivacyOverlay()">
<div class="priv-topbar">
<div class="priv-logo">S</div>
@@ -2069,12 +2064,23 @@
</div>
<div class="header">
<div class="controls">
<div class="controls" style="display:flex;align-items:center;gap:8px">
<span class="count" id="count">0 images</span>
<button class="btn" id="archiveViewBtn" onclick="toggleArchiveView()" title="View archived items">📦 Archive</button>
<a href="trash.html" class="btn" title="View database inconsistencies" style="text-decoration:none;display:inline-flex;align-items:center;">🗑️ Trash</a>
<button class="btn" id="selectBtn" onclick="toggleSelectMode()">Select</button>
<button class="btn" id="privacyBtn" onclick="togglePrivacyMode()" title="Privacy mode — hides content on focus loss (P)">🔓</button>
<!-- Inline Services LEDs on Dashboard -->
<div class="services-status-container" style="display:flex;align-items:center;gap:10px;background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.06);padding:4px 10px;border-radius:15px;font-family:monospace;font-size:10px;color:#888">
<div style="display:flex;align-items:center;gap:4px;cursor:help" id="backendStatus" title="Backend: Offline (API server is down)">
<span class="status-dot-led offline" id="backendDot"></span>
<span>API</span>
</div>
<div style="display:flex;align-items:center;gap:4px;cursor:help" id="modelStatus" title="Model-Engine: Status unknown (API server is down)">
<span class="status-dot-led offline" id="modelDot"></span>
<span>COMFY</span>
</div>
</div>
</div>
<div class="upload-zone">
<input type="file" id="uploadInput" multiple onchange="handleUpload(event)" />
@@ -2103,30 +2109,65 @@
<!-- Studio view (replaces old lightbox + faceswap modal) -->
<div id="studio" class="studio">
<!-- Topbar -->
<div class="studio-topbar">
<div class="studio-topbar-left">
<button class="btn" onclick="closeStudio()" style="font-size:11px;padding:4px 10px">✕ Gallery</button>
<button class="btn" id="lbBackBtn" onclick="jumpToPrevGroup()" style="font-size:11px;padding:4px 10px;display:none" title="Go back to previously opened group">↺ Back</button>
</div>
<div class="studio-topbar-center">
<input type="text" id="lbGroupNameInput"
style="background:transparent;border:none;border-bottom:1px solid #2a2a2a;color:#fff;font-size:13px;font-weight:600;padding:2px 5px;min-width:80px;max-width:200px;outline:none"
placeholder="Group name…"
onblur="updateGroupName()" onkeydown="if(event.key==='Enter')this.blur()"
title="Group name (applies to all images in this group)" />
<span id="lbPoseTag" style="font-size:11px;color:#f59e0b;display:none"></span>
<span id="lbImgDims" style="font-size:11px;color:#444;margin:0 8px"></span>
<span id="lbCounter" style="font-size:12px;color:#555"></span>
</div>
<div class="studio-topbar-right" style="display:flex;align-items:center;gap:6px">
<button class="btn" id="studioPrivacyBtn" onclick="togglePrivacyMode()" title="Privacy mode — hides content on focus loss (P)" style="font-size:12px;padding:4px 8px">🔓</button>
<button class="btn" id="sidebarInnerToggle" onclick="toggleSidebar()" title="Collapse/expand toolbox"></button>
</div>
</div>
<!-- Left area: topbar + main viewer + filmstrip -->
<div class="studio-left">
<!-- Topbar -->
<div class="studio-topbar">
<div class="studio-topbar-left">
<a href="./car.html" id="studioDashboardLink" class="btn" style="font-size:11px;padding:4px 10px;text-decoration:none;display:none;align-items:center;gap:4px">← Dashboard</a>
<button class="btn" id="studioGalleryBtn" onclick="closeStudio()" style="font-size:11px;padding:4px 10px">✕ Gallery</button>
<button class="btn" id="lbBackBtn" onclick="jumpToPrevGroup()" style="font-size:11px;padding:4px 10px;display:none" title="Go back to previously opened group">↺ Back</button>
</div>
<div class="studio-topbar-center" style="display:flex;align-items:center;gap:6px">
<input type="text" id="lbGroupNameInput"
style="background:transparent;border:none;border-bottom:1px solid #2a2a2a;color:#fff;font-size:13px;font-weight:600;padding:2px 5px;min-width:80px;max-width:200px;outline:none"
placeholder="Group name…"
onblur="updateGroupName()" onkeydown="if(event.key==='Enter')this.blur()"
title="Group name (applies to all images in this group)" />
<!-- Body: main viewer + sidebar -->
<div class="studio-body">
<span style="color:#444;font-size:13px">/</span>
<input type="text" id="lbTopbarImageNameInput"
style="background:transparent;border:none;border-bottom:1px solid #2a2a2a;color:#ccc;font-size:12px;padding:2px 5px;min-width:80px;max-width:180px;outline:none"
placeholder="Image name…"
onblur="updateTopbarImageName()" onkeydown="if(event.key==='Enter')this.blur()"
title="Image name / Character name" />
<div style="display:flex;gap:4px;margin-left:4px">
<button class="sb-btn" onclick="copyRawFilename()" style="padding:2px 6px;font-size:9px;min-width:auto;display:inline-flex;align-items:center;gap:3px;height:20px;border-radius:4px" title="Copy raw filename (ID) to clipboard">
<svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>ID
</button>
<button class="sb-btn" onclick="copyFilepath()" style="padding:2px 6px;font-size:9px;min-width:auto;display:inline-flex;align-items:center;gap:3px;height:20px;border-radius:4px" title="Copy absolute file path to clipboard">
<svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>Path
</button>
</div>
<span id="lbPoseTag" style="font-size:11px;color:#f59e0b;display:none"></span>
<span id="lbImgDims" style="font-size:11px;color:#444;margin:0 8px"></span>
<span id="lbCounter" style="font-size:12px;color:#555"></span>
</div>
<div class="studio-topbar-right" style="display:flex;align-items:center;gap:6px">
<!-- Inline Services LEDs on Studio Topbar -->
<div class="services-status-container" style="display:flex;align-items:center;gap:10px;background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.06);padding:4px 10px;border-radius:15px;font-family:monospace;font-size:10px;color:#888;margin-right:6px">
<div style="display:flex;align-items:center;gap:4px;cursor:help" id="studioBackendStatus" title="Backend: Status unknown">
<span class="status-dot-led offline" id="studioBackendDot"></span>
<span>API</span>
</div>
<div style="display:flex;align-items:center;gap:4px;cursor:help" id="studioModelStatus" title="Model-Engine: Status unknown">
<span class="status-dot-led offline" id="studioModelDot"></span>
<span>COMFY</span>
</div>
</div>
<button class="btn" id="studioPrivacyBtn" onclick="togglePrivacyMode()" title="Privacy mode — hides content on focus loss (P)" style="font-size:12px;padding:4px 8px">🔓</button>
<button class="btn" id="sidebarInnerToggle" onclick="toggleSidebar()" title="Collapse/expand toolbox"></button>
</div>
</div>
<!-- Main area: viewer + filmstrip -->
<div class="studio-main">
@@ -2179,19 +2220,20 @@
<option value="15000">15s</option>
</select>
</div>
</div>
</div> <!-- Close .studio-main -->
</div> <!-- Close .studio-left -->
<!-- Sidebar toolbox -->
<!-- Sidebar toolbox -->
<div id="studioSidebar" class="studio-sidebar">
<div id="sidebarInner" class="sidebar-inner">
<div class="sidebar-tabs">
<button class="sb-tab active" data-tab="info" onclick="switchSidebarTab('info')">Info</button>
<button class="sb-tab" data-tab="generate" onclick="switchSidebarTab('generate')">Ceate</button>
<button class="sb-tab" data-tab="faceswap" onclick="switchSidebarTab('faceswap')">👤</button>
<button class="sb-tab" data-tab="scenery" onclick="switchSidebarTab('scenery')">🎬</button>
<button class="sb-tab" data-tab="segment" onclick="switchSidebarTab('segment')">α</button>
<button class="sb-tab" data-tab="orbit" onclick="switchSidebarTab('orbit')" id="sbTabOrbit"></button>
<button class="sb-tab" data-tab="designer" onclick="switchSidebarTab('designer')" id="sbTabDesigner">Design</button>
<button class="sb-tab active" data-tab="info" onclick="switchSidebarTab('info')" title="Info"></button>
<button class="sb-tab" data-tab="generate" onclick="switchSidebarTab('generate')" title="Create">🪄</button>
<button class="sb-tab" data-tab="faceswap" onclick="switchSidebarTab('faceswap')" title="Faceswap">👤</button>
<button class="sb-tab" data-tab="scenery" onclick="switchSidebarTab('scenery')" title="Scenery">🎬</button>
<button class="sb-tab" data-tab="segment" onclick="switchSidebarTab('segment')" title="Segment / Background">α</button>
<button class="sb-tab" data-tab="orbit" onclick="switchSidebarTab('orbit')" id="sbTabOrbit" title="Orbit 3D"></button>
<button class="sb-tab" data-tab="designer" onclick="switchSidebarTab('designer')" id="sbTabDesigner" title="Design / Refine">🎨</button>
</div>
<div class="sb-panels">
<!-- Info panel: static HTML with existing lb* IDs so updateStudio() just works -->
@@ -2355,7 +2397,6 @@
</div>
</div>
</div>
</div>
<!-- Orbit preview modal -->
<div id="orbitModal">
@@ -2503,6 +2544,36 @@
// --- HYDRATION_START ---
const PRELOADED_IMAGES = [
"20260701_051754_image.png",
"20260701_051737_image.png",
"20260701_051721_image.png",
"20260701_051704_image.png",
"20260701_051647_image.png",
"20260701_051614_image.png",
"20260701_051541_image.png",
"20260701_051520_image.png",
"20260701_050249_image.png",
"20260701_050232_image.png",
"20260701_050215_image.png",
"20260701_050158_image.png",
"20260701_050141_image.png",
"20260701_050124_image.png",
"20260701_050108_image.png",
"20260701_050051_image.png",
"20260701_050035_image.png",
"20260701_050018_image.png",
"20260701_050001_image.png",
"20260701_045945_image.png",
"20260701_045928_image.png",
"20260701_045912_image.png",
"20260701_045838_image.png",
"20260701_045822_image.png",
"20260701_045805_image.png",
"20260701_045749_image.png",
"20260701_045732_image.png",
"20260701_045716_image.png",
"20260701_045659_1_20260619_202924_image.png",
"20260620_014953_image.png",
"20260630_130454_sc_0_20260622_101128_image.nobg.png",
"20260630_131638_sc_2_20260622_101128_image.nobg.nobg.png",
"20260630_151502_sc_0_20260622_101128_image.nobg.png",
@@ -7232,7 +7303,6 @@
"20260620_020313_2_20260618_172500_image.png",
"20260620_020302_2_20260618_172500_image.png",
"20260620_020251_2_20260618_172500_image.png",
"20260620_014953_image.png",
"20260620_014646_image.png",
"20260620_014349_image.png",
"20260620_014239_img_77.png",
@@ -7943,6 +8013,7 @@
// ============================================
let autoRefreshTimer = null;
let isInitialLoad = true;
let knownFiles = new Set();
let currentFiles = [];
let currentGroups = [];
@@ -8175,6 +8246,7 @@
// --- studio view (replaces lightbox) ---
let lbUrls = [], lbNames = [], lbIdx = 0;
let _lastRenderedFilename = null;
let _activeFilmstripTab = 'active'; // 'active', 'group', 'hidden', 'source', 'archived'
let _multiSelectModeActive = false;
const _selectedFilenames = new Set();
@@ -9123,6 +9195,23 @@
function updateStudio() {
const fname = lbNames[lbIdx];
if (fname && fname !== _lastRenderedFilename) {
// Image changed, force cancel any active text areas/edits
_lastRenderedFilename = fname;
const genPromptEl = document.getElementById('lbGenPrompt');
if (genPromptEl) {
genPromptEl.innerHTML = '';
genPromptEl.onclick = () => editMetadataField('prompt');
}
const descEl = document.getElementById('lbDescription');
if (descEl) {
descEl.innerHTML = '';
descEl.onclick = () => editMetadataField('description');
}
} else if (!fname) {
_lastRenderedFilename = null;
}
if (document.getElementById('poseCanvas')) _removePoseOverlay(); // skeleton is per-image
document.getElementById('poseResults')?.remove();
@@ -9256,6 +9345,9 @@
const nameInput = document.getElementById('lbNameInput');
if (nameInput) { nameInput.value = imageNames[fname] || ''; nameInput.placeholder = fname; }
const topbarNameInput = document.getElementById('lbTopbarImageNameInput');
if (topbarNameInput) { topbarNameInput.value = imageNames[fname] || ''; topbarNameInput.placeholder = fname; }
const clipEl = document.getElementById('lbClipDesc');
if (clipEl) clipEl.textContent = clipDescriptions[fname] || '';
@@ -9682,6 +9774,8 @@
});
if (r.ok) {
imageNames[fname] = newName;
const topbarNameInput = document.getElementById('lbTopbarImageNameInput');
if (topbarNameInput) topbarNameInput.value = newName;
showToast(`Name updated`);
}
} catch (e) {
@@ -9689,6 +9783,18 @@
}
}
async function updateTopbarImageName() {
const fname = lbNames[lbIdx];
const topbarNameInput = document.getElementById('lbTopbarImageNameInput');
if (!topbarNameInput) return;
const newName = topbarNameInput.value;
const nameInput = document.getElementById('lbNameInput');
if (nameInput) nameInput.value = newName;
await updateName();
}
async function updateGroupName() {
const gid = lbCurrentGid;
if (!gid) return;
@@ -11375,10 +11481,21 @@
async function _pollTurntableBadge() {
try {
let d = null;
try {
const r = await fetch(`${API}/output/_data/turntable_status.json?t=` + Date.now(), { signal: AbortSignal.timeout(2000) });
if (r.ok) d = await r.json();
} catch (_) {}
const gid = lbCurrentGid;
if (gid) {
try {
const r = await fetch(`${API}/turntable/status/${encodeURIComponent(gid)}`);
if (r.ok) d = await r.json();
} catch (_) {}
}
if (!d) {
try {
const r = await fetch(`${API}/output/_data/turntable_status.json?t=` + Date.now(), { signal: AbortSignal.timeout(2000) });
if (r.ok) d = await r.json();
} catch (_) {}
}
if (!d) {
const r = await fetch(`${API}/turntable/status`);
if (r.ok) d = await r.json();
@@ -11389,7 +11506,7 @@
const tbsDot = document.getElementById('tbsDot');
const tbsText = document.getElementById('tbsText');
const total = d.n_total || 0;
const done = d.n_complete || 0;
const done = (d.n_complete !== undefined ? d.n_complete : d.n_done) || 0;
if (total === 0) { badge.style.display = 'none'; }
else if (done < total) { badge.textContent = `${done}/${total}`; badge.style.display = ''; }
else { badge.style.display = 'none'; }
@@ -11413,8 +11530,15 @@
// Refresh orbit tab badge on the tab button
const tabBtn = document.getElementById('sbTabOrbit');
if (tabBtn && done > 0) {
tabBtn.textContent = `Orbit${done < total ? ' (' + done + '/' + total + ')' : ' ✓'}`;
if (tabBtn) {
tabBtn.title = `Orbit 3D (${done}/${total})`;
if (total > 0 && done < total) {
tabBtn.innerHTML = `⚛<span style="font-size: 8px; vertical-align: super; background: #fbbf24; color: #1e1b4b; padding: 1px 3px; border-radius: 6px; margin-left: 1px; display: inline-block; line-height: 1; font-weight: bold;">${done}/${total}</span>`;
} else if (total > 0 && done === total) {
tabBtn.innerHTML = `⚛<span style="font-size: 8px; vertical-align: super; background: #10b981; color: #fff; padding: 1px 3px; border-radius: 6px; margin-left: 1px; display: inline-block; line-height: 1; font-weight: bold;">✓</span>`;
} else {
tabBtn.innerHTML = ``;
}
}
} catch(_) {}
}
@@ -12155,7 +12279,8 @@
}
document.getElementById('count').textContent = `${currentGroups.length} group${currentGroups.length !== 1 ? 's' : ''} · ${fileObjects.length} images`;
if (newFileNames.size > 0) showToast(`${newFileNames.size} new image${newFileNames.size !== 1 ? 's' : ''} detected`, 'success');
if (newFileNames.size > 0 && !isInitialLoad) showToast(`${newFileNames.size} new image${newFileNames.size !== 1 ? 's' : ''} detected`, 'success');
isInitialLoad = false;
// If studio is open, sync lbUrls/lbNames from freshly-loaded groupData
const studioEl = document.getElementById('studio');
@@ -12419,18 +12544,36 @@
_backendOnline = true;
_modelEngineOnline = data.model_engine.online;
const backendClass = data.backend.busy ? 'status-dot-led busy' : 'status-dot-led online';
const backendTitle = data.backend.busy ? 'Backend: Active task running' : 'Backend: Idle (Online)';
const modelClass = data.model_engine.online ? (data.model_engine.busy ? 'status-dot-led busy' : 'status-dot-led online') : 'status-dot-led offline';
const modelTitle = data.model_engine.online
? (data.model_engine.busy ? 'Model-Engine: Active inference running' : 'Model-Engine: Idle (Online)')
: 'Model-Engine: Offline (ComfyUI backend is down)';
// Dashboard dots
const backendDot = document.getElementById('backendDot');
if (backendDot) {
backendDot.className = 'status-dot-led ' + (data.backend.busy ? 'busy' : 'online');
backendDot.parentElement.title = data.backend.busy ? 'Backend: Active task running' : 'Backend: Idle (Online)';
backendDot.className = backendClass;
backendDot.parentElement.title = backendTitle;
}
const modelDot = document.getElementById('modelDot');
if (modelDot) {
modelDot.className = 'status-dot-led ' + (data.model_engine.online ? (data.model_engine.busy ? 'busy' : 'online') : 'offline');
modelDot.parentElement.title = data.model_engine.online
? (data.model_engine.busy ? 'Model-Engine: Active inference running' : 'Model-Engine: Idle (Online)')
: 'Model-Engine: Offline (ComfyUI backend is down)';
modelDot.className = modelClass;
modelDot.parentElement.title = modelTitle;
}
// Studio topbar dots
const studioBackendDot = document.getElementById('studioBackendDot');
if (studioBackendDot) {
studioBackendDot.className = backendClass;
studioBackendDot.parentElement.title = backendTitle;
}
const studioModelDot = document.getElementById('studioModelDot');
if (studioModelDot) {
studioModelDot.className = modelClass;
studioModelDot.parentElement.title = modelTitle;
}
} else {
throw new Error("API responded with error");
@@ -12439,15 +12582,32 @@
_backendOnline = false;
_modelEngineOnline = false;
const offlineClass = 'status-dot-led offline';
const backendOfflineTitle = 'Backend: Offline (API server is down)';
const modelOfflineTitle = 'Model-Engine: Status unknown (API server is down)';
// Dashboard dots
const backendDot = document.getElementById('backendDot');
if (backendDot) {
backendDot.className = 'status-dot-led offline';
backendDot.parentElement.title = 'Backend: Offline (API server is down)';
backendDot.className = offlineClass;
backendDot.parentElement.title = backendOfflineTitle;
}
const modelDot = document.getElementById('modelDot');
if (modelDot) {
modelDot.className = 'status-dot-led offline';
modelDot.parentElement.title = 'Model-Engine: Status unknown (API server is down)';
modelDot.className = offlineClass;
modelDot.parentElement.title = modelOfflineTitle;
}
// Studio topbar dots
const studioBackendDot = document.getElementById('studioBackendDot');
if (studioBackendDot) {
studioBackendDot.className = offlineClass;
studioBackendDot.parentElement.title = backendOfflineTitle;
}
const studioModelDot = document.getElementById('studioModelDot');
if (studioModelDot) {
studioModelDot.className = offlineClass;
studioModelDot.parentElement.title = modelOfflineTitle;
}
}
@@ -13007,6 +13167,14 @@
document.title = PRIV_TITLE;
showPrivacyOverlay();
}
// Direct-to-dashboard back link for group specific pages
if (typeof LOAD_ONLY_GROUP_ID !== 'undefined' && LOAD_ONLY_GROUP_ID) {
const dashLink = document.getElementById('studioDashboardLink');
const galBtn = document.getElementById('studioGalleryBtn');
if (dashLink) dashLink.style.display = 'inline-flex';
if (galBtn) galBtn.style.display = 'none';
}
});
async function toggleTagAction(tagName) {
@@ -13095,7 +13263,7 @@
fileDescriptions[fname] = newDesc;
showToast('Successfully updated both prompt and description!', 'success');
updateStudio();
refreshNow();
refreshNow(true);
} else {
showToast('Failed to update prompt or description', 'error');
}
@@ -13120,17 +13288,19 @@
const el = document.getElementById(contentId);
if (!el || el.querySelector('textarea')) return;
const buttonLabel = currentVal.trim() === '' ? '✨ Create' : '✨ Refine';
el.onclick = null;
el.innerHTML = `
<textarea id="editFieldArea" style="width:100%;min-height:80px;background:#0d0d0d;border:1px solid #333;color:#ccc;font-size:11px;font-family:monospace;padding:6px;border-radius:4px;outline:none;resize:vertical;line-height:1.4;margin-bottom:4px">${currentVal}</textarea>
<textarea id="editFieldArea_${field}" style="width:100%;min-height:80px;background:#0d0d0d;border:1px solid #333;color:#ccc;font-size:11px;font-family:monospace;padding:6px;border-radius:4px;outline:none;resize:vertical;line-height:1.4;margin-bottom:4px">${currentVal}</textarea>
<div style="display:flex;gap:6px;justify-content:flex-end">
<button class="sb-btn" style="padding:2px 8px;font-size:10px" onclick="cancelEditField(event, '${field}')">Cancel</button>
<button class="sb-btn" style="padding:2px 8px;font-size:10px" onclick="refineField(event, '${field}')">✨ Refine</button>
<button class="sb-btn" style="padding:2px 8px;font-size:10px" onclick="refineField(event, '${field}')">${buttonLabel}</button>
<button class="sb-btn" style="padding:2px 8px;font-size:10px;background:#10b981;color:#fff" onclick="saveEditField(event, '${field}')">Save</button>
</div>
`;
const ta = document.getElementById('editFieldArea');
const ta = document.getElementById(`editFieldArea_${field}`);
if (ta) {
ta.focus();
ta.setSelectionRange(ta.value.length, ta.value.length);
@@ -13151,7 +13321,7 @@
const fname = lbNames[lbIdx];
if (!fname) return;
const ta = document.getElementById('editFieldArea');
const ta = document.getElementById(`editFieldArea_${field}`);
if (!ta) return;
const newVal = ta.value.trim();
@@ -13174,7 +13344,7 @@
showToast('Character & scenery description updated successfully', 'success');
}
updateStudio();
refreshNow();
refreshNow(true);
} else {
showToast('Failed to save update', 'error');
}
@@ -13189,24 +13359,30 @@
const fname = lbNames[lbIdx];
if (!fname) return;
const ta = document.getElementById('editFieldArea');
const ta = document.getElementById(`editFieldArea_${field}`);
if (!ta) return;
const currentVal = ta.value.trim();
const instruction = prompt(`Enter instructions to refine this ${field}:`, "Make it more detailed...");
const actionVerb = currentVal === '' ? 'create' : 'refine';
const instruction = prompt(`Enter instructions to ${actionVerb} this ${field}:`, actionVerb === 'create' ? "Write a detailed description of..." : "Make it more detailed...");
if (!instruction) return;
showToast('AI is refining your text...', 'info');
showToast(`AI is generating your ${field}...`, 'info');
try {
const r = await fetch(`${API}/refine-prompt`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt: currentVal, user_instruction: instruction })
body: JSON.stringify({
prompt: currentVal,
user_instruction: instruction,
filename: fname,
field: field
})
});
if (r.ok) {
const data = await r.json();
ta.value = data.refined;
showToast('Refined successfully! Click Save to apply.', 'success');
showToast('Successfully updated! Click Save to apply.', 'success');
} else {
showToast('Failed to refine field: ' + await r.text(), 'error');
}

View File

@@ -422,7 +422,8 @@ def list_persons(include_archived=False):
SELECT filename, name, group_id, clip_description,
prompt, pose, sort_order, group_name, hidden, has_background, source_refs,
has_clothing, content_type, faceswap_source_video, archived, is_source, tags,
pose_description, pose_skeleton, people_count, anatomical_completeness, facial_direction, objects, description
pose_description, pose_skeleton, people_count, anatomical_completeness, facial_direction, objects, description,
filepath
FROM person
{where}
""")

View File

@@ -1508,6 +1508,8 @@ def _write_all_static() -> None:
"anatomical_completeness": p[20],
"facial_direction": p[21],
"objects": obj_list,
"description": p[23],
"filepath": p[24],
})
print(f"[static] write_all: {len(db_images)} total images, {archived_count} archived")
try:
@@ -2144,16 +2146,15 @@ def start_multi_ref(req: MultiRefRequest):
class RefineRequest(BaseModel):
prompt: str
prompt: str = ""
filename: str | None = None
user_instruction: str | None = None
field: str | None = None
@app.post("/refine-prompt")
def refine_prompt(req: RefineRequest):
"""Refine a prompt using the external uncensored chat LLM."""
if not req.prompt:
raise HTTPException(400, "Prompt is required")
"""Refine or create a prompt/description using the external uncensored chat LLM."""
context_str = ""
if req.filename:
try:
@@ -2197,9 +2198,22 @@ def refine_prompt(req: RefineRequest):
except Exception as db_err:
print(f"[refine-prompt] database error for {req.filename}: {db_err}")
user_content = f"Refine this pose: {req.prompt}"
field_label = req.field if req.field else "text"
if req.prompt:
if req.user_instruction:
user_content = f"Refine this {field_label} according to the following instructions:\n{req.user_instruction}\n\nOriginal {field_label}:\n{req.prompt}"
else:
user_content = f"Refine this {field_label} to make it more descriptive, detailed, and high-quality.\n\nOriginal {field_label}:\n{req.prompt}"
else:
# Prompt/text is empty, so we are creating one from scratch!
if req.user_instruction:
user_content = f"Create a new {field_label} from scratch according to these instructions:\n{req.user_instruction}"
else:
user_content = f"Create a new detailed and high-quality {field_label} from scratch."
if context_str:
user_content += f"\n\nUse the following image context details to ensure the refined prompt matches the reference characteristics closely:\n{context_str}"
user_content += f"\n\nUse the following image context details to ensure the output matches the reference characteristics closely:\n{context_str}"
# Use the same API as gen_poses.py
llm_api = "http://192.168.1.160:8001/v1/chat/completions"
@@ -2221,7 +2235,8 @@ def refine_prompt(req: RefineRequest):
try:
database.save_db_prompt("refine", refined, {
"original": req.prompt,
"filename": req.filename
"filename": req.filename,
"field": req.field
})
except Exception as db_err:
print(f"[refine-prompt] failed to save to prompt table: {db_err}")
@@ -2712,6 +2727,7 @@ def list_images(archived: bool = False, bypass_static: bool = False):
"facial_direction": p[21],
"objects": obj_list,
"description": p[23] if len(p) > 23 else None,
"filepath": p[24] if len(p) > 24 else None,
})
db_images.sort(
key=lambda x: _get_cached_file_meta(x["filename"], output_dir)[1],

View File

@@ -878,6 +878,7 @@ Anatomically precise, hyperrealistic, high detail, keep the characteristics of t
# Lantern:
you are in a black void.
kneeling, knees apart, sitting back on the heels.
torso upright, spine long.
arms extended overhead, hands clasped or holding an imaginary object.
@@ -889,6 +890,7 @@ Anatomically precise, hyperrealistic, high detail, keep the characteristics of t
# Compass:
you are in a black void.
seated, one leg extended straight forward.
other leg bent, foot placed on the inner thigh of the extended leg.
torso folded forward over the extended leg.
@@ -900,6 +902,7 @@ Anatomically precise, hyperrealistic, high detail, keep the characteristics of t
# Vessel (beta):
you are in a black void.
lying on back, knees bent, feet flat on the ground.
arms extended overhead, hands clasped.
hips lifted slightly, lower back arched.
@@ -911,6 +914,7 @@ Anatomically precise, hyperrealistic, high detail, keep the characteristics of t
# Quiver (beta):
you are in a black void.
lying on stomach, legs together, arms at the sides.
torso lifted in a backbend, chest and shoulders arching upward.
head thrown back, neck stretched.
@@ -922,6 +926,7 @@ Perfect anatomy, hyperrealistic
# Loom:
you are in a black void.
seated, legs extended straight forward, feet flexed.
torso upright, spine long.
arms extended forward, hands reaching toward the feet.
@@ -3051,7 +3056,7 @@ You are standing in black void.
A single solid steel device, polished and seamless.
An internal plug, contoured and smooth, connected by a short rigid bar to a small external shield covering your vulva.
From the shield, a thin rigid steel bar rises up your abdomen and sternum, terminating in a collar locked around your neck.
One continuous piece — internal plug, connecting bar, collar.
One continuous piece — internal plug, connecting bar, collar.
Your wrists cuffed to the vertical bar at your hips.
Your ankles cuffed together.
The device occupies and seals simultaneously — internal and external denial in one form.
@@ -4637,4 +4642,5 @@ Your gaze is locked onto the viewer, a look of defiance and anticipation. The po
power, the steel bars creating an asymmetrical silhouette. Perfect anatomy, realistic.
# nice pose
standing, feet apart, hands on the hips. torso twisted, one shoulder forward, one back. weight shifted to one leg, opposite hip popped. waist compressed, creating a pronounced hourglass silhouette. head turned to look over the shoulder. Looking back into camera. Pinched, exaggerated. Perfect anatomy, realistic