updates UI
This commit is contained in:
@@ -77,6 +77,10 @@ Scenery:
|
|||||||
- add a 2nd visible filmstrip to easy move items from one to another strip in the UI
|
- add a 2nd visible filmstrip to easy move items from one to another strip in the UI
|
||||||
- on the landing page we now just play all images of an group. However if the orbit generaition is complete. I want to circulate those images , (this basically is a new filmstrip feature)) so we set the orbit in a filmstrip by default, but can modify it. Also we want the face to be visible in the landing page and the most prominent (most 3d alike full body with femine descriptors)) image
|
- on the landing page we now just play all images of an group. However if the orbit generaition is complete. I want to circulate those images , (this basically is a new filmstrip feature)) so we set the orbit in a filmstrip by default, but can modify it. Also we want the face to be visible in the landing page and the most prominent (most 3d alike full body with femine descriptors)) image
|
||||||
- still looking for that killer-feminine shot, one image that would show most characteristics of a nude female including eyes.
|
- still looking for that killer-feminine shot, one image that would show most characteristics of a nude female including eyes.
|
||||||
|
- scrolling past the Scne - STANDALONE / UPLOADED IMAGES still is unpleasant. I think all images get completly resized and 100x recalculated when moving the scrollbar there.
|
||||||
|
- introduce bulk select in trash.html
|
||||||
|
- tab "SAM" checkerboard icon to studio-view overlay
|
||||||
|
- in the landing page hide SOURCE images
|
||||||
-
|
-
|
||||||
## refine
|
## refine
|
||||||
|
|
||||||
|
|||||||
@@ -2011,7 +2011,7 @@
|
|||||||
<span id="lbCounter" style="font-size:12px;color:#555"></span>
|
<span id="lbCounter" style="font-size:12px;color:#555"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="studio-topbar-right">
|
<div class="studio-topbar-right">
|
||||||
<button class="btn" id="sidebarToggleTopBtn" onclick="toggleSidebar()" style="font-size:11px;padding:4px 10px" title="Toggle toolbox">Tools</button>
|
<button class="btn" id="sidebarInnerToggle" onclick="toggleSidebar()" title="Collapse/expand toolbox">⊟</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -2040,6 +2040,7 @@
|
|||||||
<button onclick="switchFilmstripTab('source')" class="fs-tab" id="fsTabSource" ondragover="event.preventDefault()" ondrop="filmstripTabDrop(event, 'SOURCE')">Source</button>
|
<button onclick="switchFilmstripTab('source')" class="fs-tab" id="fsTabSource" ondragover="event.preventDefault()" ondrop="filmstripTabDrop(event, 'SOURCE')">Source</button>
|
||||||
<button onclick="switchFilmstripTab('archived')" class="fs-tab" id="fsTabArchived" ondragover="event.preventDefault()" ondrop="filmstripTabDrop(event, 'ARCHIVED')">Archived</button>
|
<button onclick="switchFilmstripTab('archived')" class="fs-tab" id="fsTabArchived" ondragover="event.preventDefault()" ondrop="filmstripTabDrop(event, 'ARCHIVED')">Archived</button>
|
||||||
<button onclick="switchFilmstripTab('video')" class="fs-tab" id="fsTabVideo" ondragover="event.preventDefault()">Videos</button>
|
<button onclick="switchFilmstripTab('video')" class="fs-tab" id="fsTabVideo" ondragover="event.preventDefault()">Videos</button>
|
||||||
|
<button onclick="switchFilmstripTab('orbit')" class="fs-tab" id="fsTabOrbitFilter" style="display:none" ondragover="event.preventDefault()">Orbits</button>
|
||||||
</div>
|
</div>
|
||||||
<div style="display:flex;gap:6px;align-items:center" id="multiSelectControls">
|
<div style="display:flex;gap:6px;align-items:center" id="multiSelectControls">
|
||||||
<button onclick="toggleMultiSelectMode()" id="multiSelectToggleBtn" style="background:#222;border:1px solid #333;color:#888;font-size:11px;padding:2px 8px;border-radius:4px;cursor:pointer">Multi-select</button>
|
<button onclick="toggleMultiSelectMode()" id="multiSelectToggleBtn" style="background:#222;border:1px solid #333;color:#888;font-size:11px;padding:2px 8px;border-radius:4px;cursor:pointer">Multi-select</button>
|
||||||
@@ -2071,30 +2072,28 @@
|
|||||||
|
|
||||||
<!-- Sidebar toolbox -->
|
<!-- Sidebar toolbox -->
|
||||||
<div id="studioSidebar" class="studio-sidebar">
|
<div id="studioSidebar" class="studio-sidebar">
|
||||||
<button class="sidebar-toggle-btn" id="sidebarInnerToggle" onclick="toggleSidebar()" title="Collapse/expand toolbox">⊟</button>
|
|
||||||
<div id="sidebarInner" class="sidebar-inner">
|
<div id="sidebarInner" class="sidebar-inner">
|
||||||
<div class="sidebar-tabs">
|
<div class="sidebar-tabs">
|
||||||
<button class="sb-tab active" data-tab="info" onclick="switchSidebarTab('info')">Info</button>
|
<button class="sb-tab active" data-tab="info" onclick="switchSidebarTab('info')">Info</button>
|
||||||
<button class="sb-tab" data-tab="generate" onclick="switchSidebarTab('generate')">Generate</button>
|
<button class="sb-tab" data-tab="generate" onclick="switchSidebarTab('generate')">Ceate</button>
|
||||||
<button class="sb-tab" data-tab="faceswap" onclick="switchSidebarTab('faceswap')">Face</button>
|
<button class="sb-tab" data-tab="faceswap" onclick="switchSidebarTab('faceswap')">👤</button>
|
||||||
<button class="sb-tab" data-tab="scenery" onclick="switchSidebarTab('scenery')">Scene</button>
|
<button class="sb-tab" data-tab="scenery" onclick="switchSidebarTab('scenery')">🎬</button>
|
||||||
<button class="sb-tab" data-tab="segment" onclick="switchSidebarTab('segment')">SAM</button>
|
<button class="sb-tab" data-tab="segment" onclick="switchSidebarTab('segment')">α</button>
|
||||||
<button class="sb-tab" data-tab="orbit" onclick="switchSidebarTab('orbit')" id="sbTabOrbit">Orbit</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" data-tab="designer" onclick="switchSidebarTab('designer')" id="sbTabDesigner">Design</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="sb-panels">
|
<div class="sb-panels">
|
||||||
<!-- Info panel: static HTML with existing lb* IDs so updateStudio() just works -->
|
<!-- Info panel: static HTML with existing lb* IDs so updateStudio() just works -->
|
||||||
<div class="sb-panel active" id="sbPanelInfo">
|
<div class="sb-panel active" id="sbPanelInfo">
|
||||||
<div class="sb-label">Image name</div>
|
|
||||||
<input type="text" class="sb-input" id="lbNameInput"
|
|
||||||
placeholder="Image name…"
|
|
||||||
onblur="updateName()" onkeydown="if(event.key==='Enter')this.blur()"
|
|
||||||
style="margin-bottom:8px">
|
|
||||||
<div id="lbClipDesc" class="sb-status" style="margin-bottom:6px;font-size:11px;color:#555"></div>
|
|
||||||
<div id="lbGenPromptWrap" style="display:none;margin-bottom:8px">
|
<div id="lbGenPromptWrap" style="display:none;margin-bottom:8px">
|
||||||
<div class="sb-label" style="margin-bottom:3px">Generation prompt</div>
|
<div class="sb-label" style="margin-bottom:3px">Generation prompt</div>
|
||||||
<div id="lbGenPrompt" style="font-size:11px;color:#888;line-height:1.4;word-break:break-word"></div>
|
<div id="lbGenPrompt" style="font-size:11px;color:#888;line-height:1.4;word-break:break-word"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="lbSourceRefsWrap" style="display:none;margin-bottom:8px">
|
||||||
|
<div class="sb-label" style="margin-bottom:3px">Reference Images</div>
|
||||||
|
<div id="lbSourceRefs" class="sb-source-refs"></div>
|
||||||
|
</div>
|
||||||
|
<div id="lbClipDesc" class="sb-status" style="margin-bottom:6px;font-size:11px;color:#555"></div>
|
||||||
<div id="lbFaceBook" style="display:none;margin-bottom:8px">
|
<div id="lbFaceBook" style="display:none;margin-bottom:8px">
|
||||||
<div class="sb-label" style="margin-bottom:4px">Face reference</div>
|
<div class="sb-label" style="margin-bottom:4px">Face reference</div>
|
||||||
<div style="display:flex;gap:8px;align-items:flex-start">
|
<div style="display:flex;gap:8px;align-items:flex-start">
|
||||||
@@ -2167,16 +2166,11 @@
|
|||||||
<div class="sb-actions" style="margin-bottom:8px">
|
<div class="sb-actions" style="margin-bottom:8px">
|
||||||
<button class="sb-btn" onclick="tagCurrentImage()" title="Re-run tagger">Re-tag</button>
|
<button class="sb-btn" onclick="tagCurrentImage()" title="Re-run tagger">Re-tag</button>
|
||||||
<button class="sb-btn purple" id="lbUndressBtn" style="display:none" onclick="lbUndress()">Undress</button>
|
<button class="sb-btn purple" id="lbUndressBtn" style="display:none" onclick="lbUndress()">Undress</button>
|
||||||
<button class="sb-btn teal" id="lbFaceswapBtn" onclick="lbFaceswap()">Faceswap →</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="sb-actions" style="margin-bottom:8px">
|
<div class="sb-actions" style="margin-bottom:8px">
|
||||||
<button class="sb-btn" id="lbExtract" style="display:none" onclick="extractCurrentImage()">Extract</button>
|
<button class="sb-btn" id="lbExtract" style="display:none" onclick="extractCurrentImage()">Extract</button>
|
||||||
<button class="sb-btn" id="lbNoBgBtn" onclick="lbRemoveBg()">No BG</button>
|
|
||||||
<button class="sb-btn" id="lbInvertAlphaBtn" onclick="lbInvertAlpha()" title="Invert transparency — fixes when the wrong segment was kept">Invert α</button>
|
<button class="sb-btn" id="lbInvertAlphaBtn" onclick="lbInvertAlpha()" title="Invert transparency — fixes when the wrong segment was kept">Invert α</button>
|
||||||
<button class="sb-btn" id="lbCropBtn" style="display:none" onclick="lbAutoCrop()" title="Crop away transparent border">Crop</button>
|
|
||||||
<button class="sb-btn" id="lbDuplicateBtn" onclick="lbDuplicate()" title="Duplicate image into same group">Duplicate</button>
|
<button class="sb-btn" id="lbDuplicateBtn" onclick="lbDuplicate()" title="Duplicate image into same group">Duplicate</button>
|
||||||
<button class="sb-btn" id="lbManualCropBtn" onclick="startManualCrop()" title="Drag to crop image">Crop…</button>
|
|
||||||
<button class="sb-btn" id="lbManualPadBtn" onclick="startManualPad()" title="Pad to expand canvas by padding each side">Pad</button>
|
|
||||||
<button class="sb-btn" id="lbRotateLeftBtn" onclick="lbRotate(-90)" title="Rotate 90° counter-clockwise">⟲ 90°</button>
|
<button class="sb-btn" id="lbRotateLeftBtn" onclick="lbRotate(-90)" title="Rotate 90° counter-clockwise">⟲ 90°</button>
|
||||||
<button class="sb-btn" id="lbRotateRightBtn" onclick="lbRotate(90)" title="Rotate 90° clockwise">⟳ 90°</button>
|
<button class="sb-btn" id="lbRotateRightBtn" onclick="lbRotate(90)" title="Rotate 90° clockwise">⟳ 90°</button>
|
||||||
<button class="sb-btn" id="lbPoseBtn" onclick="lbTogglePose()" title="Preview body pose skeleton">Pose</button>
|
<button class="sb-btn" id="lbPoseBtn" onclick="lbTogglePose()" title="Preview body pose skeleton">Pose</button>
|
||||||
@@ -2186,7 +2180,20 @@
|
|||||||
<button class="sb-btn" id="lbArchiveBtn" onclick="lbArchive()" style="color:#f59e0b" title="Move to archive (recoverable)">Archive</button>
|
<button class="sb-btn" id="lbArchiveBtn" onclick="lbArchive()" style="color:#f59e0b" title="Move to archive (recoverable)">Archive</button>
|
||||||
<button class="sb-btn danger" id="lbDeleteBtn" onclick="lbDeleteArm()" title="Click once to arm, then again to confirm">Delete</button>
|
<button class="sb-btn danger" id="lbDeleteBtn" onclick="lbDeleteArm()" title="Click once to arm, then again to confirm">Delete</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="lbSourceRefs" class="sb-source-refs" style="display:none"></div>
|
<div class="sb-sep"></div>
|
||||||
|
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:4px">
|
||||||
|
<div class="sb-label" style="margin:0">Image name</div>
|
||||||
|
<button class="sb-btn" onclick="copyRawFilename()" style="padding:2px 6px;font-size:9px;min-width:auto;display:inline-flex;align-items:center;gap:4px" title="Copy raw filename to clipboard">
|
||||||
|
<svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="display:inline-block">
|
||||||
|
<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>Copy
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<input type="text" class="sb-input" id="lbNameInput"
|
||||||
|
placeholder="Image name…"
|
||||||
|
onblur="updateName()" onkeydown="if(event.key==='Enter')this.blur()"
|
||||||
|
style="margin-bottom:8px">
|
||||||
</div>
|
</div>
|
||||||
<!-- Other panels: rendered dynamically -->
|
<!-- Other panels: rendered dynamically -->
|
||||||
<div class="sb-panel" id="sbPanelGenerate"></div>
|
<div class="sb-panel" id="sbPanelGenerate"></div>
|
||||||
@@ -2346,6 +2353,31 @@
|
|||||||
|
|
||||||
// --- HYDRATION_START ---
|
// --- HYDRATION_START ---
|
||||||
const PRELOADED_IMAGES = [
|
const PRELOADED_IMAGES = [
|
||||||
|
"20260630_021844_view_000_000deg.png",
|
||||||
|
"20260630_022002_view_000_000deg.png",
|
||||||
|
"20260630_021951_view_000_000deg.png",
|
||||||
|
"20260630_021940_view_000_000deg.png",
|
||||||
|
"20260630_021929_view_000_000deg.png",
|
||||||
|
"20260630_021906_view_000_000deg.png",
|
||||||
|
"20260630_021855_view_000_000deg.png",
|
||||||
|
"20260630_021833_view_000_000deg.png",
|
||||||
|
"20260630_021821_view_000_000deg.png",
|
||||||
|
"20260630_021810_view_000_000deg.png",
|
||||||
|
"20260630_021759_view_000_000deg.png",
|
||||||
|
"20260630_021748_view_000_000deg.png",
|
||||||
|
"20260630_021737_view_000_000deg.png",
|
||||||
|
"20260630_021726_view_000_000deg.png",
|
||||||
|
"20260630_021715_view_000_000deg.png",
|
||||||
|
"_turntable/cg_ad73335e/views/view_000_000deg.png",
|
||||||
|
"20260630_021650_view_000_000deg.png",
|
||||||
|
"20260630_021342_sc_view_023_345deg.png",
|
||||||
|
"20260630_021047_sc_view_023_345deg.png",
|
||||||
|
"20260630_020706_sc_view_023_345deg.png",
|
||||||
|
"20260630_020632_sc_view_023_345deg.png",
|
||||||
|
"20260630_020621_sc_view_023_345deg.png",
|
||||||
|
"20260630_020517_sc_view_023_345deg.png",
|
||||||
|
"20260630_020313_sc_view_023_345deg.png",
|
||||||
|
"20260630_020143_sc_view_023_345deg.png",
|
||||||
"20260629_212135_image.png",
|
"20260629_212135_image.png",
|
||||||
"20260629_211901_dup_view_002_030deg.png",
|
"20260629_211901_dup_view_002_030deg.png",
|
||||||
"20260629_211841_dup_view_002_030deg.png",
|
"20260629_211841_dup_view_002_030deg.png",
|
||||||
@@ -2353,7 +2385,6 @@
|
|||||||
"20260629_211613_dup_view_002_030deg.png",
|
"20260629_211613_dup_view_002_030deg.png",
|
||||||
"20260629_211543_dup_view_002_030deg.png",
|
"20260629_211543_dup_view_002_030deg.png",
|
||||||
"20260629_205647_dup_view_002_030deg.png",
|
"20260629_205647_dup_view_002_030deg.png",
|
||||||
"20260629_211323_test_upload_image.png",
|
|
||||||
"20260629_211213_dup_view_002_030deg.png",
|
"20260629_211213_dup_view_002_030deg.png",
|
||||||
"20260629_210320_dup_view_002_030deg.png",
|
"20260629_210320_dup_view_002_030deg.png",
|
||||||
"20260629_210225_dup_view_002_030deg.png",
|
"20260629_210225_dup_view_002_030deg.png",
|
||||||
@@ -5219,7 +5250,6 @@
|
|||||||
"_turntable/cg_ad73335e/views/view_005_075deg.png",
|
"_turntable/cg_ad73335e/views/view_005_075deg.png",
|
||||||
"_turntable/cg_ad73335e/views/view_004_060deg.png",
|
"_turntable/cg_ad73335e/views/view_004_060deg.png",
|
||||||
"_turntable/cg_ad73335e/views/view_003_045deg.png",
|
"_turntable/cg_ad73335e/views/view_003_045deg.png",
|
||||||
"_turntable/cg_ad73335e/views/view_000_000deg.png",
|
|
||||||
"_turntable/cg_61b863a8/views/view_023_345deg.png",
|
"_turntable/cg_61b863a8/views/view_023_345deg.png",
|
||||||
"_turntable/cg_61b863a8/views/view_022_330deg.png",
|
"_turntable/cg_61b863a8/views/view_022_330deg.png",
|
||||||
"_turntable/cg_61b863a8/views/view_021_315deg.png",
|
"_turntable/cg_61b863a8/views/view_021_315deg.png",
|
||||||
@@ -7791,13 +7821,43 @@
|
|||||||
let _multiSelectModeActive = false;
|
let _multiSelectModeActive = false;
|
||||||
const _selectedFilenames = new Set();
|
const _selectedFilenames = new Set();
|
||||||
|
|
||||||
|
function isOrbitFile(fname) {
|
||||||
|
if (!fname) return false;
|
||||||
|
return fname.includes('_turntable/') || /deg\.png|deg_|\d+deg/i.test(fname);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRepresentativeLabel(n) {
|
||||||
|
if (isOrbitFile(n)) {
|
||||||
|
const m = n.match(/_(\d+)deg/i) || n.match(/(\d+)deg/i) || n.match(/view_(\d+)/i);
|
||||||
|
if (m) {
|
||||||
|
let num = m[1].replace(/^0+/, '');
|
||||||
|
if (!num) num = '0';
|
||||||
|
return `Orbit ${num}°`;
|
||||||
|
}
|
||||||
|
return 'Orbit';
|
||||||
|
}
|
||||||
|
if (n.endsWith('_face.png') || n.includes('_face.')) {
|
||||||
|
return '👤 Face Crop';
|
||||||
|
}
|
||||||
|
if (fileIsSource[n]) {
|
||||||
|
return '★ Source';
|
||||||
|
}
|
||||||
|
if (filePoses[n]) {
|
||||||
|
return filePoses[n];
|
||||||
|
}
|
||||||
|
if (isVideo(n) || fileContentType[n] === 'video') {
|
||||||
|
return '🎬 Video';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
function switchFilmstripTab(tab) {
|
function switchFilmstripTab(tab) {
|
||||||
_activeFilmstripTab = tab;
|
_activeFilmstripTab = tab;
|
||||||
|
|
||||||
// Highlight tab
|
// Highlight tab
|
||||||
['Active', 'Group', 'Hidden', 'Source', 'Archived', 'Video'].forEach(t => {
|
['Active', 'Group', 'Hidden', 'Source', 'Archived', 'Video', 'OrbitFilter'].forEach(t => {
|
||||||
const el = document.getElementById(`fsTab${t}`);
|
const el = document.getElementById(`fsTab${t}`);
|
||||||
if (el) el.classList.toggle('active', t.toLowerCase() === tab);
|
if (el) el.classList.toggle('active', t.toLowerCase().replace('filter', '') === tab);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Reset selection when switching tabs
|
// Reset selection when switching tabs
|
||||||
@@ -7810,12 +7870,13 @@
|
|||||||
const data = groupData.get(lbCurrentGid);
|
const data = groupData.get(lbCurrentGid);
|
||||||
if (data) {
|
if (data) {
|
||||||
if (tab === 'active') {
|
if (tab === 'active') {
|
||||||
// Filter out hidden, source, archived, and videos
|
// Filter out hidden, source, archived, videos, and orbits
|
||||||
const activeNames = [];
|
const activeNames = [];
|
||||||
const activeUrls = [];
|
const activeUrls = [];
|
||||||
data.names.forEach((name, i) => {
|
data.names.forEach((name, i) => {
|
||||||
const isVid = isVideo(name) || fileContentType[name] === 'video';
|
const isVid = isVideo(name) || fileContentType[name] === 'video';
|
||||||
if (!fileHidden[name] && !fileIsSource[name] && !fileArchived[name] && !isVid) {
|
const isOrb = isOrbitFile(name);
|
||||||
|
if (!fileHidden[name] && !fileIsSource[name] && !fileArchived[name] && !isVid && !isOrb) {
|
||||||
activeNames.push(name);
|
activeNames.push(name);
|
||||||
activeUrls.push(data.urls[i]);
|
activeUrls.push(data.urls[i]);
|
||||||
}
|
}
|
||||||
@@ -7847,6 +7908,8 @@
|
|||||||
matched.push(fname);
|
matched.push(fname);
|
||||||
} else if (tab === 'video' && isVid && !isArchived) {
|
} else if (tab === 'video' && isVid && !isArchived) {
|
||||||
matched.push(fname);
|
matched.push(fname);
|
||||||
|
} else if (tab === 'orbit' && isOrbitFile(fname) && !isArchived) {
|
||||||
|
matched.push(fname);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -8476,8 +8539,10 @@
|
|||||||
if (!rect) return;
|
if (!rect) return;
|
||||||
|
|
||||||
const isManual = !!document.getElementById('padBar');
|
const isManual = !!document.getElementById('padBar');
|
||||||
|
if (!isManual) return;
|
||||||
|
|
||||||
const getV = (side) => {
|
const getV = (side) => {
|
||||||
const el = document.getElementById(isManual ? 'pad' + side : 'sbPad' + side);
|
const el = document.getElementById('pad' + side);
|
||||||
return el ? el.value : '0';
|
return el ? el.value : '0';
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -8496,19 +8561,13 @@
|
|||||||
overlay.className = 'pad-preview-overlay';
|
overlay.className = 'pad-preview-overlay';
|
||||||
|
|
||||||
// Style based on selected fill mode to make preview professional & intuitive
|
// Style based on selected fill mode to make preview professional & intuitive
|
||||||
if (isManual) {
|
if (fill === 'black') {
|
||||||
if (fill === 'black') {
|
overlay.style.background = 'rgba(0, 0, 0, 0.7)';
|
||||||
overlay.style.background = 'rgba(0, 0, 0, 0.7)';
|
overlay.style.borderColor = '#444';
|
||||||
overlay.style.borderColor = '#444';
|
} else if (fill === 'white') {
|
||||||
} else if (fill === 'white') {
|
overlay.style.background = 'rgba(255, 255, 255, 0.7)';
|
||||||
overlay.style.background = 'rgba(255, 255, 255, 0.7)';
|
overlay.style.borderColor = '#ccc';
|
||||||
overlay.style.borderColor = '#ccc';
|
|
||||||
} else {
|
|
||||||
overlay.style.background = 'rgba(245, 158, 11, 0.4)';
|
|
||||||
overlay.style.borderColor = '#f59e0b';
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Sidebar is always transparent background before outpaint
|
|
||||||
overlay.style.background = 'rgba(245, 158, 11, 0.4)';
|
overlay.style.background = 'rgba(245, 158, 11, 0.4)';
|
||||||
overlay.style.borderColor = '#f59e0b';
|
overlay.style.borderColor = '#f59e0b';
|
||||||
}
|
}
|
||||||
@@ -8530,6 +8589,9 @@
|
|||||||
}
|
}
|
||||||
viewer.appendChild(overlay);
|
viewer.appendChild(overlay);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Re-render Generate tab button if active
|
||||||
|
updateSbGenBtn();
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeStudio() {
|
function closeStudio() {
|
||||||
@@ -8849,9 +8911,11 @@
|
|||||||
// Source refs
|
// Source refs
|
||||||
const refs = fileSourceRefs[fname];
|
const refs = fileSourceRefs[fname];
|
||||||
const refsDiv = document.getElementById('lbSourceRefs');
|
const refsDiv = document.getElementById('lbSourceRefs');
|
||||||
|
const refsWrap = document.getElementById('lbSourceRefsWrap');
|
||||||
if (refsDiv) {
|
if (refsDiv) {
|
||||||
if (refs && refs.length > 0) {
|
if (refs && refs.length > 0) {
|
||||||
refsDiv.style.display = 'flex';
|
refsDiv.style.display = 'flex';
|
||||||
|
if (refsWrap) refsWrap.style.display = 'block';
|
||||||
refsDiv.innerHTML = refs.map(r => {
|
refsDiv.innerHTML = refs.map(r => {
|
||||||
let srcUrl = '';
|
let srcUrl = '';
|
||||||
let isGallery = true;
|
let isGallery = true;
|
||||||
@@ -8879,6 +8943,7 @@
|
|||||||
}).join('');
|
}).join('');
|
||||||
} else {
|
} else {
|
||||||
refsDiv.style.display = 'none';
|
refsDiv.style.display = 'none';
|
||||||
|
if (refsWrap) refsWrap.style.display = 'none';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -8901,13 +8966,21 @@
|
|||||||
const prefBtn = document.getElementById('lbPreferredBtn');
|
const prefBtn = document.getElementById('lbPreferredBtn');
|
||||||
if (prefBtn) prefBtn.style.display = multi && lbIdx > 0 ? '' : 'none';
|
if (prefBtn) prefBtn.style.display = multi && lbIdx > 0 ? '' : 'none';
|
||||||
|
|
||||||
|
// Dynamically show or hide the Orbit tab filter depending on whether orbit files exist in this group
|
||||||
|
const orbitFilterBtn = document.getElementById('fsTabOrbitFilter');
|
||||||
|
if (orbitFilterBtn) {
|
||||||
|
const data = groupData.get(lbCurrentGid);
|
||||||
|
const hasOrbits = data && data.names.some(isOrbitFile);
|
||||||
|
orbitFilterBtn.style.display = hasOrbits ? '' : 'none';
|
||||||
|
}
|
||||||
|
|
||||||
// Film strip — always visible (override old display:none CSS)
|
// Film strip — always visible (override old display:none CSS)
|
||||||
const strip = document.getElementById('lbVariantStrip');
|
const strip = document.getElementById('lbVariantStrip');
|
||||||
strip.style.display = 'flex';
|
strip.style.display = 'flex';
|
||||||
const sceneryActive = _activeSidebarTab === 'scenery';
|
const sceneryActive = _activeSidebarTab === 'scenery';
|
||||||
strip.innerHTML = lbUrls.map((url, i) => {
|
strip.innerHTML = lbUrls.map((url, i) => {
|
||||||
const n = lbNames[i];
|
const n = lbNames[i];
|
||||||
const pose = filePoses[n] || '';
|
const pose = getRepresentativeLabel(n);
|
||||||
const act = i === lbIdx ? ' active' : '';
|
const act = i === lbIdx ? ' active' : '';
|
||||||
const isSel = _selectedFilenames.has(n);
|
const isSel = _selectedFilenames.has(n);
|
||||||
const selCls = isSel ? ' selected' : '';
|
const selCls = isSel ? ' selected' : '';
|
||||||
@@ -10423,9 +10496,19 @@
|
|||||||
if (btn) btn.disabled = false;
|
if (btn) btn.disabled = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function copyRawFilename() {
|
||||||
|
const fname = lbNames[lbIdx];
|
||||||
|
if (!fname) return;
|
||||||
|
navigator.clipboard.writeText(fname).then(() => {
|
||||||
|
showToast('Filename copied to clipboard', 'success');
|
||||||
|
}).catch(err => {
|
||||||
|
showToast('Failed to copy: ' + err, 'error');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function lbRemoveBg() {
|
async function lbRemoveBg() {
|
||||||
const fname = lbNames[lbIdx];
|
const fname = lbNames[lbIdx];
|
||||||
const btn = document.getElementById('lbNoBgBtn');
|
const btn = document.getElementById('lbNoBgBtn') || document.getElementById('sbNoBgBtn');
|
||||||
if (btn) { btn.disabled = true; btn.textContent = 'Removing…'; }
|
if (btn) { btn.disabled = true; btn.textContent = 'Removing…'; }
|
||||||
showToast('Removing background…');
|
showToast('Removing background…');
|
||||||
try {
|
try {
|
||||||
@@ -12028,9 +12111,8 @@
|
|||||||
}
|
}
|
||||||
if ((e.key === 'c' || e.key === 'C') && tag !== 'INPUT' && tag !== 'TEXTAREA') {
|
if ((e.key === 'c' || e.key === 'C') && tag !== 'INPUT' && tag !== 'TEXTAREA') {
|
||||||
if (document.getElementById('studio').classList.contains('open')) {
|
if (document.getElementById('studio').classList.contains('open')) {
|
||||||
const cb = document.getElementById('sbCheckerboard');
|
|
||||||
const viewer = document.getElementById('studioViewer');
|
const viewer = document.getElementById('studioViewer');
|
||||||
const cur = viewer ? viewer.classList.contains('show-checker') : (cb ? cb.checked : false);
|
const cur = viewer ? viewer.classList.contains('show-checker') : false;
|
||||||
toggleCheckerboard(!cur);
|
toggleCheckerboard(!cur);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -12352,12 +12434,6 @@
|
|||||||
const savedScrollTop = panel.scrollTop;
|
const savedScrollTop = panel.scrollTop;
|
||||||
// Preserve live input values before re-render
|
// Preserve live input values before re-render
|
||||||
const prevPromptVal = document.getElementById('sbGenPromptInput')?.value ?? '';
|
const prevPromptVal = document.getElementById('sbGenPromptInput')?.value ?? '';
|
||||||
_sbPadTop = document.getElementById('sbPadTop')?.value ?? _sbPadTop;
|
|
||||||
_sbPadRight = document.getElementById('sbPadRight')?.value ?? _sbPadRight;
|
|
||||||
_sbPadBottom = document.getElementById('sbPadBottom')?.value ?? _sbPadBottom;
|
|
||||||
_sbPadLeft = document.getElementById('sbPadLeft')?.value ?? _sbPadLeft;
|
|
||||||
_sbPadOutpaint = document.getElementById('sbPadOutpaint')?.checked ?? _sbPadOutpaint;
|
|
||||||
_sbPadUniform = document.getElementById('sbPadUniform')?.checked ?? _sbPadUniform;
|
|
||||||
_fsSelectedPoses.forEach(name => {
|
_fsSelectedPoses.forEach(name => {
|
||||||
const el = document.getElementById('sbPoseEdit_' + CSS.escape(name));
|
const el = document.getElementById('sbPoseEdit_' + CSS.escape(name));
|
||||||
if (el) _sbPoseEdits[name] = el.value;
|
if (el) _sbPoseEdits[name] = el.value;
|
||||||
@@ -12366,32 +12442,40 @@
|
|||||||
const donePoses = new Set();
|
const donePoses = new Set();
|
||||||
lbNames.forEach(n => { if (filePoses[n]) donePoses.add(filePoses[n]); });
|
lbNames.forEach(n => { if (filePoses[n]) donePoses.add(filePoses[n]); });
|
||||||
|
|
||||||
// Camera angles section — absolute positions
|
// Camera angles section — absolute positions (default collapsed)
|
||||||
const absAngles = CAMERA_ANGLES.filter(a => !a.relative);
|
const absAngles = CAMERA_ANGLES.filter(a => !a.relative);
|
||||||
const relAngles = CAMERA_ANGLES.filter(a => a.relative);
|
const relAngles = CAMERA_ANGLES.filter(a => a.relative);
|
||||||
const ORBIT_NAMES = ['Front', '¾ Left', '¾ Right', 'Side L', 'Side R', 'Back'];
|
const ORBIT_NAMES = ['Front', '¾ Left', '¾ Right', 'Side L', 'Side R', 'Back'];
|
||||||
const allOrbitSel = ORBIT_NAMES.every(n => _sbSelectedAngles.has(n));
|
const allOrbitSel = ORBIT_NAMES.every(n => _sbSelectedAngles.has(n));
|
||||||
let html = `<div style="display:flex;align-items:center;gap:6px;margin-bottom:3px">
|
let html = `
|
||||||
<span class="sb-label" style="margin:0;flex:1">Camera — absolute</span>
|
<details style="margin-bottom:8px">
|
||||||
<button class="sb-btn${allOrbitSel?' primary':''}" style="padding:2px 8px;font-size:10px" onclick="sbToggleOrbitAll()" title="Select all 6 orbit angles (front · ¾ · side · back)">${allOrbitSel?'✓ Orbit all':'Orbit all'}</button>
|
<summary class="sb-label" style="cursor:pointer;outline:none;display:flex;align-items:center;gap:6px">
|
||||||
</div><div class="sb-camera-grid" id="sbCameraGrid">`;
|
<span>Camera — absolute</span>
|
||||||
|
</summary>
|
||||||
|
<div style="display:flex;justify-content:flex-end;margin:4px 0">
|
||||||
|
<button class="sb-btn${allOrbitSel?' primary':''}" style="padding:2px 8px;font-size:10px;margin:0" onclick="sbToggleOrbitAll()" title="Select all 6 orbit angles (front · ¾ · side · back)">${allOrbitSel?'✓ Orbit all':'Orbit all'}</button>
|
||||||
|
</div>
|
||||||
|
<div class="sb-camera-grid" id="sbCameraGrid" style="margin-top:4px">`;
|
||||||
html += absAngles.map(a => {
|
html += absAngles.map(a => {
|
||||||
const sel = _sbSelectedAngles.has(a.name) ? ' selected' : '';
|
const sel = _sbSelectedAngles.has(a.name) ? ' selected' : '';
|
||||||
const done = donePoses.has(a.name) ? ' done' : '';
|
const done = donePoses.has(a.name) ? ' done' : '';
|
||||||
const nSafe = a.name.replace(/'/g, "\\'");
|
const nSafe = a.name.replace(/'/g, "\\'");
|
||||||
return `<button class="sb-angle-btn${done}${sel}" onclick="toggleSbAngle('${nSafe}')" title="${escHtml(a.prompt)}">${a.icon}<br><span style="font-size:9px">${escHtml(a.name)}</span></button>`;
|
return `<button class="sb-angle-btn${done}${sel}" onclick="toggleSbAngle('${nSafe}')" title="${escHtml(a.prompt)}">${a.icon}<br><span style="font-size:9px">${escHtml(a.name)}</span></button>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
html += '</div>';
|
html += '</div></details>';
|
||||||
|
|
||||||
// Relative rotations
|
// Relative rotations (default collapsed)
|
||||||
html += '<div class="sb-label" style="margin-top:6px">Camera — relative</div><div class="sb-camera-grid" id="sbRelAngleGrid">';
|
html += `
|
||||||
|
<details style="margin-bottom:8px">
|
||||||
|
<summary class="sb-label" style="cursor:pointer;outline:none">Camera — relative</summary>
|
||||||
|
<div class="sb-camera-grid" id="sbRelAngleGrid" style="margin-top:6px">`;
|
||||||
html += relAngles.map(a => {
|
html += relAngles.map(a => {
|
||||||
const sel = _sbSelectedAngles.has(a.name) ? ' selected' : '';
|
const sel = _sbSelectedAngles.has(a.name) ? ' selected' : '';
|
||||||
const done = donePoses.has(a.name) ? ' done' : '';
|
const done = donePoses.has(a.name) ? ' done' : '';
|
||||||
const nSafe = a.name.replace(/'/g, "\\'");
|
const nSafe = a.name.replace(/'/g, "\\'");
|
||||||
return `<button class="sb-angle-btn${done}${sel}" onclick="toggleSbAngle('${nSafe}')" title="${escHtml(a.prompt)}">${a.icon}<br><span style="font-size:9px">${escHtml(a.name)}</span></button>`;
|
return `<button class="sb-angle-btn${done}${sel}" onclick="toggleSbAngle('${nSafe}')" title="${escHtml(a.prompt)}">${a.icon}<br><span style="font-size:9px">${escHtml(a.name)}</span></button>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
html += '</div>';
|
html += '</div></details>';
|
||||||
|
|
||||||
// Wireframe pose reference
|
// Wireframe pose reference
|
||||||
const wfSel = _sbWireframeRef || '';
|
const wfSel = _sbWireframeRef || '';
|
||||||
@@ -12485,18 +12569,6 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a brand-new pose (persisted to poses.md).
|
|
||||||
html += `<div class="sb-sep"></div>
|
|
||||||
<details style="margin-bottom:6px">
|
|
||||||
<summary style="font-size:11px;color:#888;cursor:pointer">+ New pose</summary>
|
|
||||||
<input type="text" id="sbNewPoseName" placeholder="Pose name"
|
|
||||||
style="width:100%;box-sizing:border-box;background:#111;border:1px solid #2a2a2a;border-radius:5px;color:#ccc;font-size:11px;padding:6px;margin:6px 0 4px">
|
|
||||||
<textarea id="sbNewPoseText" placeholder="Prompt text"
|
|
||||||
style="width:100%;box-sizing:border-box;background:#111;border:1px solid #2a2a2a;border-radius:5px;color:#ccc;font-size:11px;padding:6px;resize:vertical;min-height:48px"></textarea>
|
|
||||||
<label style="font-size:10px;color:#777;display:flex;align-items:center;gap:3px;cursor:pointer;margin-top:4px"><input type="checkbox" id="sbNewPoseBeta">beta</label>
|
|
||||||
<button class="sb-btn primary" style="margin-top:4px;font-size:10px;padding:3px 10px" onclick="addNewPose()">Add pose</button>
|
|
||||||
</details>`;
|
|
||||||
|
|
||||||
// Sticky footer — always visible without scrolling: refs + custom prompt + Generate
|
// Sticky footer — always visible without scrolling: refs + custom prompt + Generate
|
||||||
html += `<div class="sb-gen-footer">`;
|
html += `<div class="sb-gen-footer">`;
|
||||||
if (_sbRefIndices.length > 0) {
|
if (_sbRefIndices.length > 0) {
|
||||||
@@ -12526,33 +12598,7 @@
|
|||||||
: 'One edit per selected reference.'}</div>`;
|
: 'One edit per selected reference.'}</div>`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
html += `<div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap;margin-bottom:8px">
|
html += `<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
|
||||||
<span style="font-size:11px;color:#666" title="Expand canvas before generation — gives the model space to outpaint">Pad:</span>
|
|
||||||
${['Top','Right','Bottom','Left'].map(side => {
|
|
||||||
const val = side==='Top'?_sbPadTop:side==='Right'?_sbPadRight:side==='Bottom'?_sbPadBottom:_sbPadLeft;
|
|
||||||
const onInput = `syncPadFields(this)`;
|
|
||||||
const onBlur = `this.value = formatPadValue(this.value); ${onInput}`;
|
|
||||||
const label = side === 'Top' ? 'T:' : side === 'Right' ? 'R:' : side === 'Bottom' ? 'B:' : 'L:';
|
|
||||||
return `<label style="display:flex;align-items:center;gap:2px;font-size:11px;color:#888">${label}
|
|
||||||
<input type="text" id="sbPad${side}" value="${val}" placeholder="px or %"
|
|
||||||
style="width:48px;background:#111;border:1px solid #2a2a2a;color:#ccc;border-radius:4px;padding:2px 4px;font-size:11px;text-align:right"
|
|
||||||
oninput="${onInput}" onblur="${onBlur}"
|
|
||||||
onkeydown="if(event.key === 'Enter') { submitSbGenerate(); event.preventDefault(); }"></label>`;
|
|
||||||
}).join('')}
|
|
||||||
<label style="display:flex;align-items:center;gap:2px;font-size:11px;color:#888;cursor:pointer" title="Keep all padding values equal">
|
|
||||||
<input type="checkbox" id="sbPadUniform" ${_sbPadUniform?'checked':''} onchange="togglePadUniform(this)" style="cursor:pointer">Uniform</label>
|
|
||||||
<button class="sb-btn" style="padding:2px 5px;font-size:9px;margin:0" onclick="applyPadPreset('10%', true)">+10%</button>
|
|
||||||
<button class="sb-btn" style="padding:2px 5px;font-size:9px;margin:0" onclick="applyPadPreset('20%', true)">+20%</button>
|
|
||||||
<button class="sb-btn" style="padding:2px 5px;font-size:9px;margin:0" onclick="applyPadPreset('50%', true)">+50%</button>
|
|
||||||
<button class="sb-btn" style="padding:2px 5px;font-size:9px;margin:0" onclick="applyPadPreset('1:1', true)">1:1</button>
|
|
||||||
<button class="sb-btn" style="padding:2px 5px;font-size:9px;margin:0" onclick="applyPadPreset('4:3', true)">4:3</button>
|
|
||||||
<button class="sb-btn" style="padding:2px 5px;font-size:9px;margin:0" onclick="applyPadPreset('3:4', true)">3:4</button>
|
|
||||||
<button class="sb-btn" style="padding:2px 5px;font-size:9px;margin:0" onclick="applyPadPreset('16:9', true)">16:9</button>
|
|
||||||
<button class="sb-btn" style="padding:2px 5px;font-size:9px;margin:0;background:#3b0764;color:#d8b4fe;border-color:#581c87" onclick="applyPadPreset('0', true)">Reset</button>
|
|
||||||
<label style="display:flex;align-items:center;gap:3px;font-size:11px;color:#888;cursor:pointer" title="Instruct the model to fill in the padded area">
|
|
||||||
<input type="checkbox" id="sbPadOutpaint" ${(_sbPadOutpaint??false)?'checked':''} onchange="updateSbGenBtn()" style="cursor:pointer">Outpaint</label>
|
|
||||||
</div>
|
|
||||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
|
|
||||||
<span id="sbGenJobStatus" style="flex:1;font-size:11px;color:#888;display:none;min-width:120px">
|
<span id="sbGenJobStatus" style="flex:1;font-size:11px;color:#888;display:none;min-width:120px">
|
||||||
<span class="sb-spinner"></span><span id="sbGenJobText">Generating…</span>
|
<span class="sb-spinner"></span><span id="sbGenJobText">Generating…</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -12628,13 +12674,14 @@
|
|||||||
if (el) _sbPoseEdits[name] = el.value;
|
if (el) _sbPoseEdits[name] = el.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refetch poses from the backend, then re-render both the Generate tab and batch menu.
|
// Refetch poses from the backend, then re-render both the Generate tab, Design tab and batch menu.
|
||||||
async function _reloadPosesUI() {
|
async function _reloadPosesUI() {
|
||||||
try {
|
try {
|
||||||
const r = await fetch(`${API}/poses`);
|
const r = await fetch(`${API}/poses`);
|
||||||
if (r.ok) availablePoses = await r.json();
|
if (r.ok) availablePoses = await r.json();
|
||||||
} catch (e) { /* non-fatal */ }
|
} catch (e) { /* non-fatal */ }
|
||||||
renderSidebarGenerate();
|
renderSidebarGenerate();
|
||||||
|
renderSidebarDesigner();
|
||||||
if (document.getElementById('poseMenu')) renderPoseMenu();
|
if (document.getElementById('poseMenu')) renderPoseMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -12694,6 +12741,12 @@
|
|||||||
});
|
});
|
||||||
if (r.ok) {
|
if (r.ok) {
|
||||||
showToast('Pose added', 'success');
|
showToast('Pose added', 'success');
|
||||||
|
const nEl = document.getElementById('sbNewPoseName');
|
||||||
|
const tEl = document.getElementById('sbNewPoseText');
|
||||||
|
const bEl = document.getElementById('sbNewPoseBeta');
|
||||||
|
if (nEl) nEl.value = '';
|
||||||
|
if (tEl) tEl.value = '';
|
||||||
|
if (bEl) bEl.checked = false;
|
||||||
await _reloadPosesUI();
|
await _reloadPosesUI();
|
||||||
} else {
|
} else {
|
||||||
showToast('Add failed: ' + await r.text(), 'error');
|
showToast('Add failed: ' + await r.text(), 'error');
|
||||||
@@ -12764,7 +12817,7 @@
|
|||||||
}, 400);
|
}, 400);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submitSingleAngle(prompt) {
|
async function submitSingleAngle(prompt, angleName) {
|
||||||
if (!_fsModelFilename) return;
|
if (!_fsModelFilename) return;
|
||||||
const bar = document.getElementById('studioAngleBar');
|
const bar = document.getElementById('studioAngleBar');
|
||||||
if (bar) bar.style.pointerEvents = 'none';
|
if (bar) bar.style.pointerEvents = 'none';
|
||||||
@@ -12774,7 +12827,7 @@
|
|||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
filenames: [_fsModelFilename], prompt: [prompt],
|
filenames: [_fsModelFilename], prompt: [prompt],
|
||||||
poses: [null], seed: -1, max_area: 0, group_id: lbCurrentGid,
|
poses: [angleName || null], seed: -1, max_area: 0, group_id: lbCurrentGid,
|
||||||
wireframe_ref: _sbWireframeRef || null,
|
wireframe_ref: _sbWireframeRef || null,
|
||||||
wireframe_time: _sbWireframeTime,
|
wireframe_time: _sbWireframeTime,
|
||||||
}),
|
}),
|
||||||
@@ -12811,7 +12864,8 @@
|
|||||||
const overlay = CAMERA_ANGLES.slice(0, 6);
|
const overlay = CAMERA_ANGLES.slice(0, 6);
|
||||||
bar.innerHTML = overlay.map(a => {
|
bar.innerHTML = overlay.map(a => {
|
||||||
const pSafe = a.prompt.replace(/'/g, "\\'");
|
const pSafe = a.prompt.replace(/'/g, "\\'");
|
||||||
return `<button onclick="submitSingleAngle('${pSafe}')" title="${escHtml(a.name)}">${a.icon}</button>`;
|
const nSafe = a.name.replace(/'/g, "\\'");
|
||||||
|
return `<button onclick="submitSingleAngle('${pSafe}', '${nSafe}')" title="${escHtml(a.name)}">${a.icon}</button>`;
|
||||||
}).join('') + `
|
}).join('') + `
|
||||||
<button onclick="startManualPad()" title="Pad canvas (Expand image)" style="color:#f59e0b">
|
<button onclick="startManualPad()" title="Pad canvas (Expand image)" style="color:#f59e0b">
|
||||||
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin:auto;display:block">
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin:auto;display:block">
|
||||||
@@ -12841,18 +12895,38 @@
|
|||||||
<path d="M6 2v14a2 2 0 0 0 2 2h14M18 22V8a2 2 0 0 0-2-2H2"/>
|
<path d="M6 2v14a2 2 0 0 0 2 2h14M18 22V8a2 2 0 0 0-2-2H2"/>
|
||||||
<path d="M10 10l4 4m0-4l-4 4" stroke-width="1.5"/>
|
<path d="M10 10l4 4m0-4l-4 4" stroke-width="1.5"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
</button>
|
||||||
|
<button id="overlayCheckerboardBtn" onclick="toggleCheckerboard(!document.getElementById('studioViewer').classList.contains('show-checker'))" title="Toggle Checkerboard (show transparency)">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin:auto;display:block">
|
||||||
|
<rect x="3" y="3" width="18" height="18" rx="2" ry="2"/>
|
||||||
|
<path d="M9 3v18m6-18v18M3 9h18M3 15h18"/>
|
||||||
|
</svg>
|
||||||
</button>`;
|
</button>`;
|
||||||
|
|
||||||
|
// Synchronize the button's visual state on render
|
||||||
|
const isChecked = document.getElementById('studioViewer')?.classList.contains('show-checker');
|
||||||
|
const overlayBtn = document.getElementById('overlayCheckerboardBtn');
|
||||||
|
if (overlayBtn) {
|
||||||
|
overlayBtn.style.color = isChecked ? '#10b981' : '#f43f5e';
|
||||||
|
overlayBtn.style.background = isChecked ? 'rgba(16,185,129,0.1)' : '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateSbGenBtn() {
|
function updateSbGenBtn() {
|
||||||
const btn = document.getElementById('sbGenBtn');
|
const btn = document.getElementById('sbGenBtn');
|
||||||
if (!btn) return;
|
if (!btn) return;
|
||||||
const hasPrompt = (document.getElementById('sbGenPromptInput')?.value || '').trim().length > 0;
|
const hasPrompt = (document.getElementById('sbGenPromptInput')?.value || '').trim().length > 0;
|
||||||
const getV = (id) => document.getElementById(id)?.value || '0';
|
|
||||||
const padValStr = getV('sbPadTop') + getV('sbPadRight') + getV('sbPadBottom') + getV('sbPadLeft');
|
let hasPaddingAction = false;
|
||||||
const hasPadding = padValStr.replace(/[0%\s]/g, '').length > 0;
|
const padBar = document.getElementById('padBar');
|
||||||
const outpaint = document.getElementById('sbPadOutpaint')?.checked;
|
if (padBar) {
|
||||||
const hasPaddingAction = hasPadding && outpaint;
|
const getV = (id) => document.getElementById(id)?.value || '0';
|
||||||
|
const padValStr = getV('padTop') + getV('padRight') + getV('padBottom') + getV('padLeft');
|
||||||
|
const hasPadding = padValStr.replace(/[0%\s]/g, '').length > 0;
|
||||||
|
const outpaint = document.getElementById('padOutpaint')?.checked;
|
||||||
|
hasPaddingAction = hasPadding && outpaint;
|
||||||
|
}
|
||||||
|
|
||||||
const total = _fsSelectedPoses.size + _sbSelectedAngles.size + (hasPrompt ? 1 : 0) + (hasPaddingAction && !hasPrompt ? 1 : 0);
|
const total = _fsSelectedPoses.size + _sbSelectedAngles.size + (hasPrompt ? 1 : 0) + (hasPaddingAction && !hasPrompt ? 1 : 0);
|
||||||
btn.disabled = total === 0;
|
btn.disabled = total === 0;
|
||||||
btn.textContent = total > 1 ? `Generate (${total})` : 'Generate';
|
btn.textContent = total > 1 ? `Generate (${total})` : 'Generate';
|
||||||
@@ -12986,20 +13060,29 @@
|
|||||||
if (!_fsModelFilename) return;
|
if (!_fsModelFilename) return;
|
||||||
const promptVal = (document.getElementById('sbGenPromptInput')?.value || '').trim();
|
const promptVal = (document.getElementById('sbGenPromptInput')?.value || '').trim();
|
||||||
const hasPoses = _fsSelectedPoses.size > 0 || _sbSelectedAngles.size > 0;
|
const hasPoses = _fsSelectedPoses.size > 0 || _sbSelectedAngles.size > 0;
|
||||||
const getV = (id) => document.getElementById(id)?.value || '0';
|
|
||||||
const padValStr = getV('sbPadTop') + getV('sbPadRight') + getV('sbPadBottom') + getV('sbPadLeft');
|
const padBar = document.getElementById('padBar');
|
||||||
const hasPadding = padValStr.replace(/[0%\s]/g, '').length > 0;
|
let hasPaddingAction = false;
|
||||||
const outpaint = document.getElementById('sbPadOutpaint')?.checked;
|
if (padBar) {
|
||||||
const hasPaddingAction = hasPadding && outpaint;
|
const getV = (id) => document.getElementById(id)?.value || '0';
|
||||||
|
const padValStr = getV('padTop') + getV('padRight') + getV('padBottom') + getV('padLeft');
|
||||||
if (!hasPoses && !hasPaddingAction) {
|
const hasPadding = padValStr.replace(/[0%\s]/g, '').length > 0;
|
||||||
// If no poses or padding, we'll run base_prompts
|
const outpaint = document.getElementById('padOutpaint')?.checked;
|
||||||
|
hasPaddingAction = hasPadding && outpaint;
|
||||||
|
|
||||||
|
_sbPadTop = document.getElementById('padTop')?.value || '0';
|
||||||
|
_sbPadRight = document.getElementById('padRight')?.value || '0';
|
||||||
|
_sbPadBottom = document.getElementById('padBottom')?.value || '0';
|
||||||
|
_sbPadLeft = document.getElementById('padLeft')?.value || '0';
|
||||||
|
_sbPadOutpaint = outpaint || false;
|
||||||
|
} else {
|
||||||
|
_sbPadTop = '0';
|
||||||
|
_sbPadRight = '0';
|
||||||
|
_sbPadBottom = '0';
|
||||||
|
_sbPadLeft = '0';
|
||||||
|
_sbPadOutpaint = false;
|
||||||
}
|
}
|
||||||
_sbPadTop = document.getElementById('sbPadTop')?.value || '0';
|
|
||||||
_sbPadRight = document.getElementById('sbPadRight')?.value || '0';
|
|
||||||
_sbPadBottom = document.getElementById('sbPadBottom')?.value || '0';
|
|
||||||
_sbPadLeft = document.getElementById('sbPadLeft')?.value || '0';
|
|
||||||
_sbPadOutpaint = document.getElementById('sbPadOutpaint')?.checked || false;
|
|
||||||
const prompts = [], poses = [];
|
const prompts = [], poses = [];
|
||||||
_fsSelectedPoses.forEach(name => {
|
_fsSelectedPoses.forEach(name => {
|
||||||
poses.push(name);
|
poses.push(name);
|
||||||
@@ -13860,8 +13943,8 @@
|
|||||||
const isSelected = _sceneRefs[0] && _sceneRefs[0].kind === 'wireframe' && _sceneRefs[0].name === frame;
|
const isSelected = _sceneRefs[0] && _sceneRefs[0].kind === 'wireframe' && _sceneRefs[0].name === frame;
|
||||||
const borderStyle = isSelected ? 'border:2px solid #8b5cf6' : 'border:1px solid #27272a';
|
const borderStyle = isSelected ? 'border:2px solid #8b5cf6' : 'border:1px solid #27272a';
|
||||||
html += `
|
html += `
|
||||||
<div style="cursor:pointer;position:relative" title="${escHtml(frame)}" onclick="sceneSelectWireframeImage('${fSafe}')">
|
<div style="cursor:pointer;position:relative;width:40px;height:40px;flex-shrink:0" title="${escHtml(frame)}" onclick="sceneSelectWireframeImage('${fSafe}')">
|
||||||
<img src="${API}/wireframe/${encodeURIComponent(frame)}" loading="lazy" decoding="async" style="width:40px;height:40px;object-fit:cover;border-radius:4px;${borderStyle}" onerror="this.style.opacity=.3">
|
<img src="${API}/wireframe/${encodeURIComponent(frame)}" width="40" height="40" loading="lazy" decoding="async" style="width:40px;height:40px;object-fit:cover;border-radius:4px;${borderStyle}" onerror="this.style.opacity=.3">
|
||||||
</div>`;
|
</div>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -13897,8 +13980,8 @@
|
|||||||
const isSelected = _sceneRefs[0] && _sceneRefs[0].kind === 'wireframe' && _sceneRefs[0].name === img;
|
const isSelected = _sceneRefs[0] && _sceneRefs[0].kind === 'wireframe' && _sceneRefs[0].name === img;
|
||||||
const borderStyle = isSelected ? 'border:2px solid #2563eb' : 'border:1px solid #333';
|
const borderStyle = isSelected ? 'border:2px solid #2563eb' : 'border:1px solid #333';
|
||||||
html += `
|
html += `
|
||||||
<div style="cursor:pointer;position:relative" title="${escHtml(img)}" onclick="sceneSelectWireframeImage('${imgSafe}')">
|
<div style="cursor:pointer;position:relative;width:48px;height:48px;flex-shrink:0" title="${escHtml(img)}" onclick="sceneSelectWireframeImage('${imgSafe}')">
|
||||||
<img src="${API}/wireframe/${encodeURIComponent(img)}" loading="lazy" decoding="async" style="width:48px;height:48px;object-fit:cover;border-radius:4px;${borderStyle}" onerror="this.style.opacity=.3">
|
<img src="${API}/wireframe/${encodeURIComponent(img)}" width="48" height="48" loading="lazy" decoding="async" style="width:48px;height:48px;object-fit:cover;border-radius:4px;${borderStyle}" onerror="this.style.opacity=.3">
|
||||||
</div>`;
|
</div>`;
|
||||||
});
|
});
|
||||||
html += '</div>';
|
html += '</div>';
|
||||||
@@ -14006,10 +14089,10 @@
|
|||||||
let groupHtml = `<div style="display:flex;flex-wrap:wrap;gap:4px">`;
|
let groupHtml = `<div style="display:flex;flex-wrap:wrap;gap:4px">`;
|
||||||
groupHtml += visibleItems.map(it => {
|
groupHtml += visibleItems.map(it => {
|
||||||
const url = `${API}/output/` + encodeURIComponent(it.filename);
|
const url = `${API}/output/` + encodeURIComponent(it.filename);
|
||||||
return `<div style="display:inline-block;cursor:pointer;position:relative;vertical-align:top"
|
return `<div style="display:inline-block;cursor:pointer;position:relative;vertical-align:top;width:48px;height:48px;flex-shrink:0"
|
||||||
title="Use as background · ${escHtml(it.filename)}${it.group_id?' ('+escHtml(it.group_id)+')':''}"
|
title="Use as background · ${escHtml(it.filename)}${it.group_id?' ('+escHtml(it.group_id)+')':''}"
|
||||||
onclick="sceneLibUseAsBackground(${JSON.stringify(it.filename)})">
|
onclick="sceneLibUseAsBackground(${JSON.stringify(it.filename)})">
|
||||||
<img src="${url}" style="width:48px;height:48px;object-fit:cover;border-radius:4px;border:1px solid #27272a;background:#09090b"
|
<img src="${url}" width="48" height="48" style="width:48px;height:48px;object-fit:cover;border-radius:4px;border:1px solid #27272a;background:#09090b"
|
||||||
onerror="this.style.opacity=.3">
|
onerror="this.style.opacity=.3">
|
||||||
</div>`;
|
</div>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
@@ -14468,15 +14551,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div style="display:flex;gap:8px;margin-bottom:8px;flex-wrap:wrap">
|
<div style="display:flex;gap:8px;margin-bottom:8px;flex-wrap:wrap">
|
||||||
<button class="sb-btn" id="sbSam2Btn" onclick="sam2RemoveBg()" title="SAM2 segmentation — preserves hair/glasses/accessories; falls back to rembg if unavailable">Remove BG (SAM2)</button>
|
<button class="sb-btn" id="sbSam2Btn" onclick="sam2RemoveBg()" title="SAM2 segmentation — preserves hair/glasses/accessories; falls back to rembg if unavailable">Remove BG (SAM2)</button>
|
||||||
|
<button class="sb-btn" id="sbNoBgBtn" onclick="lbRemoveBg()" title="rembg BG removal, overwrites in-place">No BG</button>
|
||||||
<button class="sb-btn" id="sbRembgBtn" onclick="rembgRemoveBg()" title="rembg (U2Net) — fast background removal without SAM2">Remove BG (rembg)</button>
|
<button class="sb-btn" id="sbRembgBtn" onclick="rembgRemoveBg()" title="rembg (U2Net) — fast background removal without SAM2">Remove BG (rembg)</button>
|
||||||
<button class="sb-btn" id="sbRestoreBgBtn" onclick="restoreBg()">Restore BG</button>
|
<button class="sb-btn" id="sbRestoreBgBtn" onclick="restoreBg()">Restore BG</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="sbSegStatus" style="font-size:11px;color:#aaa;min-height:16px;margin-bottom:10px"></div>
|
<div id="sbSegStatus" style="font-size:11px;color:#aaa;min-height:16px;margin-bottom:10px"></div>
|
||||||
<div class="sb-sep"></div>
|
|
||||||
<label style="display:flex;align-items:center;gap:8px;font-size:12px;cursor:pointer;margin-bottom:6px">
|
|
||||||
<input type="checkbox" id="sbCheckerboard" onchange="toggleCheckerboard(this.checked)" ${isChecked ? 'checked' : ''}>
|
|
||||||
<span>Checkerboard (show transparency)</span>
|
|
||||||
</label>
|
|
||||||
<div style="font-size:10px;color:#555;line-height:1.4">
|
<div style="font-size:10px;color:#555;line-height:1.4">
|
||||||
SAM2 merges all non-border masks (preserves hair, glasses, sandals).<br>
|
SAM2 merges all non-border masks (preserves hair, glasses, sandals).<br>
|
||||||
rembg (U2Net) is faster and works without SAM2 checkpoint.<br>
|
rembg (U2Net) is faster and works without SAM2 checkpoint.<br>
|
||||||
@@ -14623,8 +14702,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const cb = document.getElementById('sbCheckerboard');
|
const overlayBtn = document.getElementById('overlayCheckerboardBtn');
|
||||||
if (cb) cb.checked = on;
|
if (overlayBtn) {
|
||||||
|
overlayBtn.style.color = on ? '#10b981' : '#f43f5e';
|
||||||
|
overlayBtn.style.background = on ? 'rgba(16,185,129,0.1)' : '';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- set preferred ----
|
// ---- set preferred ----
|
||||||
@@ -14846,6 +14928,19 @@
|
|||||||
} else if (_designerResults !== null) {
|
} else if (_designerResults !== null) {
|
||||||
html += `<div style="font-size:11px;color:#ff6b6b;padding:8px 0">No valid poses were returned by the AI. Try adjusting guidelines.</div>`;
|
html += `<div style="font-size:11px;color:#ff6b6b;padding:8px 0">No valid poses were returned by the AI. Try adjusting guidelines.</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add a brand-new pose (persisted to poses.md).
|
||||||
|
html += `<div class="sb-sep"></div>
|
||||||
|
<details style="margin-bottom:6px">
|
||||||
|
<summary style="font-size:11px;color:#888;cursor:pointer">+ New pose</summary>
|
||||||
|
<input type="text" id="sbNewPoseName" placeholder="Pose name"
|
||||||
|
style="width:100%;box-sizing:border-box;background:#111;border:1px solid #2a2a2a;border-radius:5px;color:#ccc;font-size:11px;padding:6px;margin:6px 0 4px">
|
||||||
|
<textarea id="sbNewPoseText" placeholder="Prompt text"
|
||||||
|
style="width:100%;box-sizing:border-box;background:#111;border:1px solid #2a2a2a;border-radius:5px;color:#ccc;font-size:11px;padding:6px;resize:vertical;min-height:48px"></textarea>
|
||||||
|
<label style="font-size:10px;color:#777;display:flex;align-items:center;gap:3px;cursor:pointer;margin-top:4px"><input type="checkbox" id="sbNewPoseBeta">beta</label>
|
||||||
|
<button class="sb-btn primary" style="margin-top:4px;font-size:10px;padding:3px 10px" onclick="addNewPose()">Add pose</button>
|
||||||
|
</details>`;
|
||||||
|
|
||||||
html += `</div>`;
|
html += `</div>`;
|
||||||
|
|
||||||
panel.innerHTML = html;
|
panel.innerHTML = html;
|
||||||
|
|||||||
Reference in New Issue
Block a user