updates UI

This commit is contained in:
mike
2026-06-30 01:07:54 +02:00
parent 61268de34b
commit ad9a2ae078
13 changed files with 1375 additions and 397 deletions

View File

@@ -2039,6 +2039,7 @@
<button onclick="switchFilmstripTab('hidden')" class="fs-tab" id="fsTabHidden" ondragover="event.preventDefault()" ondrop="filmstripTabDrop(event, 'HIDDEN')">Hidden</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('video')" class="fs-tab" id="fsTabVideo" ondragover="event.preventDefault()">Videos</button>
</div>
<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>
@@ -2137,13 +2138,24 @@
<span id="lbDeepPeopleCount" style="font-weight:bold;color:#f59e0b">-</span>
</div>
<div style="display:flex;justify-content:space-between;align-items:center">
<span style="color:#888">Completeness:</span>
<span style="color:#888">Anatomical Completeness:</span>
<span id="lbDeepCompleteness" style="font-weight:bold;color:#f59e0b">-</span>
</div>
<div id="lbDeepOutpaintRecommendationWrap" style="display:none;font-size:10px;background:rgba(245,158,11,0.1);border:1px dashed rgba(245,158,11,0.3);padding:6px;border-radius:4px;color:#f59e0b;line-height:1.3;margin-top:2px;margin-bottom:2px">
<span id="lbDeepOutpaintRecommendation"></span>
</div>
<div style="display:flex;justify-content:space-between;align-items:center">
<span style="color:#888">Face Direction:</span>
<span style="color:#888">Camera Angle:</span>
<span id="lbDeepCameraAngle" style="font-weight:bold;color:#f59e0b">-</span>
</div>
<div style="display:flex;justify-content:space-between;align-items:center">
<span style="color:#888">Face & Gaze Direction:</span>
<span id="lbDeepFacialDirection" style="font-weight:bold;color:#f59e0b">-</span>
</div>
<div style="display:flex;justify-content:space-between;align-items:center">
<span style="color:#888">Face Status:</span>
<span id="lbDeepFaceStatus" style="font-weight:bold;color:#10b981">Not Detected</span>
</div>
<div style="margin-top:4px">
<div style="color:#888;margin-bottom:4px">Detected objects:</div>
<div id="lbDeepObjects" style="display:flex;flex-wrap:wrap;gap:4px"></div>
@@ -2334,9 +2346,161 @@
// --- HYDRATION_START ---
const PRELOADED_IMAGES = [
"20260619_040244_5_20260619_040135_image.png",
"20260629_212135_image.png",
"20260629_211901_dup_view_002_030deg.png",
"20260629_211841_dup_view_002_030deg.png",
"20260629_211821_dup_view_002_030deg.png",
"20260629_211613_dup_view_002_030deg.png",
"20260629_211543_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_210320_dup_view_002_030deg.png",
"20260629_210225_dup_view_002_030deg.png",
"20260629_205723_dup_view_002_030deg.png",
"20260629_203653_view_022_330deg.png",
"_turntable/up_e7e12f38/views/view_023_345deg.png",
"_turntable/up_e7e12f38/views/view_022_330deg.png",
"_turntable/up_e7e12f38/views/view_021_315deg.png",
"_turntable/up_e7e12f38/views/view_020_300deg.png",
"_turntable/up_e7e12f38/views/view_019_285deg.png",
"_turntable/up_e7e12f38/views/view_018_270deg.png",
"_turntable/up_e7e12f38/views/view_017_255deg.png",
"_turntable/up_e7e12f38/views/view_016_240deg.png",
"cg_36a33bae_face.png",
"20260629_193733_image.png",
"20260629_193704_image.png",
"20260629_193611_image.png",
"_turntable/up_e7e12f38/views/view_015_225deg.png",
"_turntable/up_e7e12f38/views/view_014_210deg.png",
"_turntable/up_e7e12f38/views/view_013_195deg.png",
"_turntable/up_e7e12f38/views/view_012_180deg.png",
"_turntable/up_e7e12f38/views/view_011_165deg.png",
"20260629_193310_image.png",
"_turntable/up_e7e12f38/views/view_010_150deg.png",
"_turntable/up_e7e12f38/views/view_009_135deg.png",
"_turntable/up_e7e12f38/views/view_008_120deg.png",
"20260629_192810_image.png",
"_turntable/up_e7e12f38/views/view_007_105deg.png",
"_turntable/up_e7e12f38/views/view_006_090deg.png",
"_turntable/up_e7e12f38/views/view_005_075deg.png",
"_turntable/up_e7e12f38/views/view_004_060deg.png",
"_turntable/up_e7e12f38/views/view_003_045deg.png",
"_turntable/up_e7e12f38/views/view_002_030deg.png",
"_turntable/up_e7e12f38/views/view_001_015deg.png",
"_turntable/up_e7e12f38/views/view_000_000deg.png",
"20260629_192507_12_20260629_192154_Screenshot_From_2026-06-29_18-54-57.png",
"20260629_192456_11_20260629_192154_Screenshot_From_2026-06-29_18-54-57.png",
"20260629_192445_10_20260629_192154_Screenshot_From_2026-06-29_18-54-57.png",
"20260629_192433_9_20260629_192154_Screenshot_From_2026-06-29_18-54-57.png",
"20260629_192422_8_20260629_192154_Screenshot_From_2026-06-29_18-54-57.png",
"20260629_192410_7_20260629_192154_Screenshot_From_2026-06-29_18-54-57.png",
"20260629_192359_6_20260629_192154_Screenshot_From_2026-06-29_18-54-57.png",
"20260629_192347_5_20260629_192154_Screenshot_From_2026-06-29_18-54-57.png",
"20260629_192336_4_20260629_192154_Screenshot_From_2026-06-29_18-54-57.png",
"20260629_192325_3_20260629_192154_Screenshot_From_2026-06-29_18-54-57.png",
"20260629_192314_2_20260629_192154_Screenshot_From_2026-06-29_18-54-57.png",
"20260629_192303_1_20260629_192154_Screenshot_From_2026-06-29_18-54-57.png",
"20260629_192251_0_20260629_192154_Screenshot_From_2026-06-29_18-54-57.png",
"20260629_192154_Screenshot_From_2026-06-29_18-54-57.png",
"_turntable/up_c385e55b/views/view_023_345deg.png",
"_turntable/up_c385e55b/views/view_022_330deg.png",
"_turntable/up_c385e55b/views/view_021_315deg.png",
"_turntable/up_c385e55b/views/view_020_300deg.png",
"_turntable/up_c385e55b/views/view_019_285deg.png",
"_turntable/up_c385e55b/views/view_018_270deg.png",
"_turntable/up_c385e55b/views/view_017_255deg.png",
"_turntable/up_c385e55b/views/view_016_240deg.png",
"_turntable/up_c385e55b/views/view_015_225deg.png",
"_turntable/up_c385e55b/views/view_014_210deg.png",
"_turntable/up_c385e55b/views/view_013_195deg.png",
"_turntable/up_c385e55b/views/view_012_180deg.png",
"_turntable/up_c385e55b/views/view_011_165deg.png",
"_turntable/up_c385e55b/views/view_010_150deg.png",
"_turntable/up_c385e55b/views/view_009_135deg.png",
"_turntable/up_c385e55b/views/view_008_120deg.png",
"_turntable/up_c385e55b/views/view_007_105deg.png",
"_turntable/up_c385e55b/views/view_006_090deg.png",
"_turntable/up_c385e55b/views/view_005_075deg.png",
"_turntable/up_c385e55b/views/view_004_060deg.png",
"_turntable/up_c385e55b/views/view_003_045deg.png",
"_turntable/up_c385e55b/views/view_002_030deg.png",
"_turntable/up_c385e55b/views/view_001_015deg.png",
"_turntable/up_c385e55b/views/view_000_000deg.png",
"up_c385e55b_face.png",
"20260629_174128_12_20260629_173620_image.png",
"20260629_174112_11_20260629_173620_image.png",
"20260629_174055_10_20260629_173620_image.png",
"20260629_174038_9_20260629_173620_image.png",
"20260629_174021_8_20260629_173620_image.png",
"20260629_174004_7_20260629_173620_image.png",
"20260629_173948_6_20260629_173620_image.png",
"20260629_173914_4_20260629_173620_image.png",
"20260629_173857_3_20260629_173620_image.png",
"20260629_173841_2_20260629_173620_image.png",
"20260629_173824_1_20260629_173620_image.png",
"20260629_173807_0_20260629_173620_image.png",
"20260629_173620_image.png",
"_turntable/up_5e5db95d/views/view_023_345deg.png",
"_turntable/up_5e5db95d/views/view_022_330deg.png",
"_turntable/up_5e5db95d/views/view_021_315deg.png",
"_turntable/up_5e5db95d/views/view_020_300deg.png",
"_turntable/up_5e5db95d/views/view_019_285deg.png",
"_turntable/up_5e5db95d/views/view_018_270deg.png",
"_turntable/up_5e5db95d/views/view_017_255deg.png",
"_turntable/up_5e5db95d/views/view_016_240deg.png",
"_turntable/up_5e5db95d/views/view_015_225deg.png",
"_turntable/up_5e5db95d/views/view_014_210deg.png",
"_turntable/up_5e5db95d/views/view_013_195deg.png",
"_turntable/up_5e5db95d/views/view_012_180deg.png",
"_turntable/up_5e5db95d/views/view_011_165deg.png",
"_turntable/up_5e5db95d/views/view_010_150deg.png",
"_turntable/up_5e5db95d/views/view_009_135deg.png",
"_turntable/up_5e5db95d/views/view_008_120deg.png",
"_turntable/up_5e5db95d/views/view_007_105deg.png",
"_turntable/up_5e5db95d/views/view_006_090deg.png",
"up_78ae5271_face.png",
"_turntable/up_5e5db95d/views/view_005_075deg.png",
"_turntable/up_5e5db95d/views/view_004_060deg.png",
"img_87.png_face.png",
"_turntable/up_5e5db95d/views/view_003_045deg.png",
"_turntable/up_5e5db95d/views/view_002_030deg.png",
"_turntable/up_5e5db95d/views/view_001_015deg.png",
"_turntable/up_5e5db95d/views/view_000_000deg.png",
"20260629_164213_12_20260629_163755_image.png",
"20260629_164202_11_20260629_163755_image.png",
"20260629_164151_10_20260629_163755_image.png",
"20260629_164140_9_20260629_163755_image.png",
"20260629_164129_8_20260629_163755_image.png",
"20260629_164118_7_20260629_163755_image.png",
"20260629_164107_6_20260629_163755_image.png",
"20260629_164056_5_20260629_163755_image.png",
"20260629_164045_4_20260629_163755_image.png",
"20260629_164031_3_20260629_163755_image.png",
"20260629_164020_2_20260629_163755_image.png",
"20260629_164003_1_20260629_163755_image.png",
"20260629_163944_0_20260629_163755_image.png",
"20260629_163755_image.png",
"20260629_152011_sc_image.png",
"20260629_151844_sc_image.png",
"20260629_151508_sc_image.png",
"20260629_151413_sc_image.png",
"20260629_151327_sc_image.png",
"20260629_151239_sc_view_000_000deg.png",
"20260629_150255_sc_image.png",
"20260629_150202_sc_image.png",
"20260629_150105_sc_dup_20260621_032709_image.nobg.nobg.png",
"20260629_145957_sc_image.nobg.png",
"20260629_114346_sc_sc_image.nobg.nobg.png",
"20260629_145638_sc_image.png",
"20260629_145454_sc_image.png",
"20260629_143122_sc_sc_1_20260619_040135_image.png",
"20260629_142746_sc_sc_1_20260619_040135_image.png",
"20260629_142612_sc_view_023_345deg.png",
"20260629_142432_sc_view_023_345deg.png",
"_turntable/cg_f85e7ae1/views/view_023_345deg.png",
"20260629_141838_sc_1_20260619_040135_image.png",
"cg_f85e7ae1_face.png",
"20260618_181804_8_20260618_181600_image.png",
"b1.png_face.png",
"_turntable/b1.png/views/view_023_345deg.nobg.png",
"cg_e6ba3260_face.png",
@@ -2350,7 +2514,6 @@
"20260629_131834_sc_1_20260619_123958_image.png",
"20260629_124948_sc_view_023_345deg.png",
"20260629_124523_sc_view_023_345deg.png",
"20260629_124433_sc_view_023_345deg.png",
"20260629_122907_fs_Step Sis will do anything to make me Delete this Videos_1633s-2269s_image.png_prev50.mp4",
"20260629_120459_fs_bitch insemination_dup_20260626_003125_dup_Pasted image (9).png.mp4",
"20260629_114451_sc_sc_image.nobg.png",
@@ -2442,14 +2605,11 @@
"20260629_063000_1_20260629_062932_image.png",
"20260629_062949_0_20260629_062932_image.png",
"20260629_062932_image.png",
"20260629_060926_12_20260629_060652_image.png",
"20260629_060915_11_20260629_060652_image.png",
"20260629_060904_10_20260629_060652_image.png",
"20260629_060853_9_20260629_060652_image.png",
"20260629_060842_8_20260629_060652_image.png",
"20260629_060831_7_20260629_060652_image.png",
"20260629_060820_6_20260629_060652_image.png",
"20260629_060809_5_20260629_060652_image.png",
"20260629_060747_3_20260629_060652_image.png",
"20260629_060736_2_20260629_060652_image.png",
"20260629_060725_1_20260629_060652_image.png",
@@ -2506,7 +2666,11 @@
"20260629_033631_sc_2_20260622_101128_image.nobg.png",
"20260629_032345_mr_sc_dup_20260624_055327_image.nobg.png",
"20260629_032136_mr_sc_dup_20260624_055327_image.nobg.png",
"20260629_032057_mr_sc_dup_20260624_055327_image.nobg.png",
"20260629_031800_mr_sc_dup_20260624_055327_image.nobg.png",
"20260629_031650_mr_sc_dup_20260624_055327_image.nobg.png",
"20260629_031559_mr_sc_dup_20260624_055327_image.nobg.png",
"20260629_031435_image.png",
"20260629_031259_image.png",
"20260629_030904_sc_dup_20260624_055327_image.nobg.png",
"20260629_030742_sc_dup_20260624_055327_image.nobg.png",
@@ -3190,7 +3354,6 @@
"20260627_204815_image.png",
"20260627_204804_image.png",
"20260627_204756_image.png",
"_turntable/cg_f85e7ae1/views/view_023_345deg.png",
"_turntable/cg_f85e7ae1/views/view_013_195deg.png",
"_turntable/cg_f85e7ae1/views/view_012_180deg.png",
"_turntable/cg_f85e7ae1/views/view_010_150deg.png",
@@ -4712,7 +4875,6 @@
"_turntable/20260618_181600_image.png/views/view_001_030deg.png",
"20260626_234334_dup_20260626_231655_sc_image.png",
"20260626_231655_sc_image.png",
"20260626_231349_sc_image.png",
"20260626_231127_image.png",
"_turntable/up_624995c3/views/view_011_330deg.png",
"_turntable/up_624995c3/views/view_010_300deg.png",
@@ -5468,8 +5630,8 @@
"20260625_025335_dup_20260625_024438_dup_20260624_165109_0_20260622_091731_image.nobg.png",
"20260625_023536_dup_20260624_165109_0_20260622_091731_image.nobg.png",
"20260624_165109_0_20260622_091731_image.nobg.png",
"20260624_165109_0_20260622_091731_image.png",
"20260625_023536_dup_20260624_165109_0_20260622_091731_image.png",
"20260624_165109_0_20260622_091731_image.png",
"up_ab858b0a_face.png",
"20260625_022332_dup_20260625_022250_sc_image.png",
"20260625_022250_sc_image.png",
@@ -5668,8 +5830,8 @@
"20260624_182958_image.png",
"20260624_174900_image.png",
"20260624_173115_image.png",
"20260624_175711_dup_20260624_174943_dup_20260624_173115_image.png",
"20260624_174943_dup_20260624_173115_image.png",
"20260624_175711_dup_20260624_174943_dup_20260624_173115_image.png",
"20260624_174757_image.png",
"20260624_174442_image.png",
"20260624_172923_image.nobg.png",
@@ -5785,8 +5947,8 @@
"20260624_143700_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_143647_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_143635_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_151613_dup_20260624_143523_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_144952_dup_20260624_143523_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_151613_dup_20260624_143523_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_143024_mr_image.png",
"20260624_142959_mr_image.png",
"20260624_142923_mr_image.png",
@@ -6966,6 +7128,7 @@
"20260619_040319_8_20260619_040135_image.png",
"20260619_040307_7_20260619_040135_image.png",
"20260619_040255_6_20260619_040135_image.png",
"20260619_040244_5_20260619_040135_image.png",
"20260619_040233_4_20260619_040135_image.png",
"20260619_040221_3_20260619_040135_image.png",
"20260619_040135_image.png",
@@ -7632,7 +7795,7 @@
_activeFilmstripTab = tab;
// Highlight tab
['Active', 'Group', 'Hidden', 'Source', 'Archived'].forEach(t => {
['Active', 'Group', 'Hidden', 'Source', 'Archived', 'Video'].forEach(t => {
const el = document.getElementById(`fsTab${t}`);
if (el) el.classList.toggle('active', t.toLowerCase() === tab);
});
@@ -7647,11 +7810,12 @@
const data = groupData.get(lbCurrentGid);
if (data) {
if (tab === 'active') {
// Filter out hidden, source, and archived
// Filter out hidden, source, archived, and videos
const activeNames = [];
const activeUrls = [];
data.names.forEach((name, i) => {
if (!fileHidden[name] && !fileIsSource[name] && !fileArchived[name]) {
const isVid = isVideo(name) || fileContentType[name] === 'video';
if (!fileHidden[name] && !fileIsSource[name] && !fileArchived[name] && !isVid) {
activeNames.push(name);
activeUrls.push(data.urls[i]);
}
@@ -7669,6 +7833,7 @@
const isArchived = !!fileArchived[fname];
const isHidden = !!fileHidden[fname];
const isSource = !!fileIsSource[fname];
const isVid = isVideo(fname) || fileContentType[fname] === 'video';
if (fileGroups[fname] !== lbCurrentGid) {
return;
@@ -7680,6 +7845,8 @@
matched.push(fname);
} else if (tab === 'archived' && isArchived) {
matched.push(fname);
} else if (tab === 'video' && isVid && !isArchived) {
matched.push(fname);
}
});
@@ -8810,7 +8977,11 @@
const deepMetaWrap = document.getElementById('lbDeepMetaWrap');
const deepPeopleCount = document.getElementById('lbDeepPeopleCount');
const deepCompleteness = document.getElementById('lbDeepCompleteness');
const deepOutpaintRecommendationWrap = document.getElementById('lbDeepOutpaintRecommendationWrap');
const deepOutpaintRecommendation = document.getElementById('lbDeepOutpaintRecommendation');
const deepCameraAngle = document.getElementById('lbDeepCameraAngle');
const deepFacialDirection = document.getElementById('lbDeepFacialDirection');
const deepFaceStatus = document.getElementById('lbDeepFaceStatus');
const deepObjects = document.getElementById('lbDeepObjects');
if (deepMetaWrap) {
const count = filePeopleCount[fname];
@@ -8824,17 +8995,152 @@
deepPeopleCount.textContent = count !== null ? `${count} person(s)` : '-';
}
if (deepCompleteness) {
deepCompleteness.textContent = compl !== null ? (compl ? 'Complete / Full body' : 'Partial / Close-up') : '-';
if (compl !== null) {
deepCompleteness.textContent = compl ? 'Complete / Full body' : 'Partial / Close-up';
deepCompleteness.style.color = compl ? '#10b981' : '#f59e0b';
let outpaintRecText = '';
let outpaintRecStyle = 'rgba(245,158,11,0.1)';
let outpaintRecBorder = 'rgba(245,158,11,0.3)';
let outpaintRecColor = '#f59e0b';
if (!compl) {
const kpts = filePoseSkeleton[fname];
let parsedKpts = null;
if (kpts) {
try {
parsedKpts = typeof kpts === 'string' ? JSON.parse(kpts) : kpts;
} catch(e) {}
}
if (parsedKpts && parsedKpts.length >= 17) {
const minScore = 0.3;
const hasHead = parsedKpts.slice(0, 5).some(k => k && k[2] >= minScore);
const hasShoulders = (parsedKpts[5] && parsedKpts[5][2] >= minScore) || (parsedKpts[6] && parsedKpts[6][2] >= minScore);
const hasHips = (parsedKpts[11] && parsedKpts[11][2] >= minScore) || (parsedKpts[12] && parsedKpts[12][2] >= minScore);
const hasKnees = (parsedKpts[13] && parsedKpts[13][2] >= minScore) || (parsedKpts[14] && parsedKpts[14][2] >= minScore);
const hasAnkles = (parsedKpts[15] && parsedKpts[15][2] >= minScore) || (parsedKpts[16] && parsedKpts[16][2] >= minScore);
const imgEl = document.getElementById('lbImg');
const imgW = imgEl ? imgEl.naturalWidth : 0;
const imgH = imgEl ? imgEl.naturalHeight : 0;
let issues = [];
if (hasHead && !hasHips && !hasKnees && !hasAnkles) {
issues.push("Lower body is completely missing/cropped (portrait/bust view)");
} else if (hasHead && hasHips && !hasKnees && !hasAnkles) {
issues.push("Lower legs and knees are missing/cropped (waist-up/navel-up view)");
} else if (hasHead && hasHips && hasKnees && !hasAnkles) {
issues.push("Feet and ankles are missing/cropped (knee-up view)");
} else if (!hasHead && hasShoulders) {
issues.push("Head is cropped or out of frame");
}
if (imgW > 0 && imgH > 0) {
const headKpts = parsedKpts.slice(0, 5).filter(k => k && k[2] >= minScore);
if (headKpts.length > 0) {
const minY = Math.min(...headKpts.map(k => k[1]));
if (minY < imgH * 0.03) {
issues.push("Head/hair is cut off at the top edge");
}
}
const ankleKpts = parsedKpts.slice(15, 17).filter(k => k && k[2] >= minScore);
if (ankleKpts.length > 0) {
const maxY = Math.max(...ankleKpts.map(k => k[1]));
if (maxY > imgH * 0.97) {
issues.push("Feet are cut off at the bottom edge");
}
}
const wristKpts = parsedKpts.slice(9, 11).filter(k => k && k[2] >= minScore);
if (wristKpts.length > 0) {
const minX = Math.min(...wristKpts.map(k => k[0]));
const maxX = Math.max(...wristKpts.map(k => k[0]));
if (minX < imgW * 0.03 || maxX > imgW * 0.97) {
issues.push("Arms or hands are cut off at the side edges");
}
}
}
if (issues.length > 0) {
outpaintRecText = `💡 <strong>Recommendation:</strong> Crop detected: <strong>${issues.join(', ')}</strong>.<br/>`;
let suggestions = [];
if (issues.some(i => i.includes("Lower body") || i.includes("Lower legs") || i.includes("Feet") || i.includes("bottom"))) {
suggestions.push("expand the <strong>Bottom</strong> by 20% to 50% using <strong>Pad</strong> and enable <strong>Outpaint</strong>");
}
if (issues.some(i => i.includes("Head") || i.includes("top"))) {
suggestions.push("expand the <strong>Top</strong> by 15% to 25% using <strong>Pad</strong> and enable <strong>Outpaint</strong>");
}
if (issues.some(i => i.includes("side") || i.includes("Arms"))) {
suggestions.push("expand the <strong>Left/Right</strong> sides by 15% to 25% using <strong>Pad</strong> and enable <strong>Outpaint</strong>");
}
if (suggestions.length > 0) {
outpaintRecText += `To fix, ${suggestions.join(' and ')} to naturally complete the body and limbs.`;
} else {
outpaintRecText += `Expand canvas by 20% to 50% using <strong>Pad</strong> and enable <strong>Outpaint</strong> to render the missing anatomical structure.`;
}
}
}
if (!outpaintRecText) {
outpaintRecText = '💡 <strong>Recommendation:</strong> Anatomy is cropped/partial. Expand canvas by 20% to 50% using <strong>Pad</strong> and enable <strong>Outpaint</strong> to render missing limbs and complete the body anatomy.';
}
} else {
outpaintRecText = '✓ <strong>Recommendation:</strong> Anatomy is complete. No outpainting required!';
outpaintRecStyle = 'rgba(16,185,129,0.1)';
outpaintRecBorder = 'rgba(16,185,129,0.3)';
outpaintRecColor = '#10b981';
}
if (deepOutpaintRecommendationWrap && deepOutpaintRecommendation) {
deepOutpaintRecommendationWrap.style.display = 'block';
deepOutpaintRecommendationWrap.style.background = outpaintRecStyle;
deepOutpaintRecommendationWrap.style.borderColor = outpaintRecBorder;
deepOutpaintRecommendationWrap.style.color = outpaintRecColor;
deepOutpaintRecommendation.innerHTML = outpaintRecText;
}
} else {
deepCompleteness.textContent = '-';
deepCompleteness.style.color = '#ccc';
if (deepOutpaintRecommendationWrap) deepOutpaintRecommendationWrap.style.display = 'none';
}
}
if (deepCameraAngle) {
let camAngle = '-';
const promptLower = (filePrompts[fname] || '').toLowerCase();
if (promptLower.includes('front') || promptLower.includes('straight-on')) camAngle = 'Straight-on / Frontal';
else if (promptLower.includes('¾ left') || promptLower.includes('three-quarter left')) camAngle = '¾ Left View';
else if (promptLower.includes('¾ right') || promptLower.includes('three-quarter right')) camAngle = '¾ Right View';
else if (promptLower.includes('side l') || promptLower.includes('left profile')) camAngle = 'Left Profile / Side View';
else if (promptLower.includes('side r') || promptLower.includes('right profile')) camAngle = 'Right Profile / Side View';
else if (promptLower.includes('back') || promptLower.includes('rear')) camAngle = 'Rear / Back View';
else if (promptLower.includes('high angle') || promptLower.includes('bird\'s-eye')) camAngle = 'High / Bird\'s-eye View';
else if (promptLower.includes('low angle') || promptLower.includes('worm\'s-eye')) camAngle = 'Low / Worm\'s-eye View';
else if (filePoses[fname]) camAngle = filePoses[fname] + ' (Preset)';
deepCameraAngle.textContent = camAngle;
}
if (deepFacialDirection) {
deepFacialDirection.textContent = faceDir || '-';
deepFacialDirection.textContent = faceDir ? faceDir.toUpperCase() : '-';
}
if (deepFaceStatus) {
if (faceDir) {
deepFaceStatus.textContent = 'Detected & Gaze Analyzed';
deepFaceStatus.style.color = '#10b981';
} else {
deepFaceStatus.textContent = 'Not Detected / Pending';
deepFaceStatus.style.color = '#f59e0b';
}
}
if (deepObjects) {
if (objs.length > 0) {
deepObjects.innerHTML = objs.map(o => {
const tag = typeof o === 'string' ? o : (o.tag || '');
const score = typeof o === 'object' && o.score ? ` (${Math.round(o.score * 100)}%)` : '';
return `<span style="background:#27272a;border:1px solid #3f3f46;border-radius:4px;padding:2px 6px;margin:2px;font-size:10px;display:inline-block;color:#ccc">${escHtml(tag)}${score}</span>`;
const titleStr = (typeof o === 'object' && o.bbox) ? ` title="BBox: [${o.bbox.join(', ')}]"` : '';
return `<span style="background:#27272a;border:1px solid #3f3f46;border-radius:4px;padding:2px 6px;margin:2px;font-size:10px;display:inline-block;color:#ccc;cursor:help"${titleStr}>${escHtml(tag)}${score}</span>`;
}).join('');
} else {
deepObjects.innerHTML = '<span style="color:#555;font-size:10px">None detected</span>';
@@ -11757,6 +12063,13 @@
e.preventDefault();
return;
}
} else if (_activeSidebarTab === 'designer') {
const btn = document.getElementById('designerGenerateBtn');
if (btn && !btn.disabled) {
btn.click();
e.preventDefault();
return;
}
}
}
}
@@ -12274,7 +12587,12 @@
function showPromptSuggest(el, boxId = 'sbPromptSuggest') {
const box = document.getElementById(boxId);
if (!box) return;
const histKey = boxId === 'scenePromptSuggest' ? 'scenePromptHistory' : 'batchPromptHistory';
let histKey = 'batchPromptHistory';
if (boxId === 'scenePromptSuggest') {
histKey = 'scenePromptHistory';
} else if (boxId === 'designerPromptSuggest') {
histKey = 'designerPromptHistory';
}
const hist = JSON.parse(localStorage.getItem(histKey) || '[]');
const q = (el.value || '').trim().toLowerCase();
const matches = (q ? hist.filter(p => p.toLowerCase().includes(q) && p.toLowerCase() !== q) : hist).slice(0, 40);
@@ -12300,6 +12618,7 @@
el.value = item.dataset.val;
hidePromptSuggest(boxId);
if (inputId === 'sbGenPromptInput') updateSbGenBtn();
if (inputId === 'designerContextInput') _designerContext = el.value;
autoGrowPrompt(el);
el.focus();
}
@@ -12499,6 +12818,29 @@
<path d="M5 3h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2z"/>
<path d="M12 8v8m-4-4h8"/>
</svg>
</button>
<button onclick="lbTogglePose()" title="Toggle body pose skeleton" style="color:#60a5fa">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin:auto;display:block">
<circle cx="12" cy="4" r="2"/>
<path d="M12 6v7m-3-4h6m-3 4l-2 5m2-5l2 5"/>
</svg>
</button>
<button onclick="lbDuplicate()" title="Duplicate image" style="color:#10b981">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin:auto;display:block">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>
</svg>
</button>
<button onclick="startManualCrop()" title="Crop... (Manual crop)" style="color:#a78bfa">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin:auto;display:block">
<path d="M6 2v14a2 2 0 0 0 2 2h14M18 22V8a2 2 0 0 0-2-2H2"/>
</svg>
</button>
<button onclick="lbAutoCrop()" title="Crop (Auto-crop)" style="color:#8b5cf6">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="margin:auto;display:block">
<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"/>
</svg>
</button>`;
}
@@ -12563,6 +12905,7 @@
});
if (r.ok) {
const { refined } = await r.json();
savePromptHistory(prompt);
// Append refined prompt to original
// User said: (original prompt+ gen_poses.py refined prompt)
el.value = (el.value.trim() + "\n\n" + refined).trim();
@@ -12621,6 +12964,7 @@
});
if (r.ok) {
const { refined } = await r.json();
savePromptHistory(prompt);
textarea.value = refined;
_sbPoseEdits[name] = refined;
textarea.style.height = 'auto';
@@ -14008,6 +14352,7 @@
});
if (r.ok) {
const { refined } = await r.json();
savePromptHistory(prompt, 'scenePromptHistory');
el.value = (el.value.trim() + "\n\n" + refined).trim();
autoGrowPrompt(el);
showToast('Prompt refined', 'success');
@@ -14398,13 +14743,23 @@
let _designerNum = 3;
let _designerUseActiveImg = true;
let _designerResults = null; // hold generated poses {"Name": "body"}
let _designerHistory = []; // array of {"role": "user"|"assistant", "content": string}
function renderSidebarDesigner() {
const panel = document.getElementById('sbPanelDesigner');
if (!panel) return;
const savedScrollTop = panel.scrollTop;
let html = `<div class="sb-label">Prompt Designer</div>
let turnCount = "";
if (_designerHistory && _designerHistory.length > 0) {
const turn = Math.ceil(_designerHistory.length / 2) + 1;
turnCount = `<span style="font-size:9px;background:#8b5cf6;color:white;padding:2px 6px;border-radius:4px;margin-left:auto;font-weight:bold">Turn ${turn}</span>`;
}
let html = `<div style="display:flex;align-items:center;margin-bottom:4px">
<div class="sb-label" style="margin:0">Prompt Designer</div>
${turnCount}
</div>
<div style="font-size:11px;color:#777;margin-bottom:10px;line-height:1.4">
Generate multiple custom adult photographic pose prompts using the uncensored Mistral-24B model based on specific context guidelines or active image features.
</div>`;
@@ -14415,15 +14770,33 @@
style="margin-bottom:10px;background:#111;color:#ccc;border:1px solid #2a2a2a"
onchange="_designerNum = parseInt(this.value) || 3">`;
// Specific Context inputs
html += `<div class="sb-label">Context / Guidelines</div>
<textarea id="designerContextInput" class="sb-input" rows="4"
placeholder="e.g. standing pose, legs spread, hands bound behind back, explicit photography..."
style="resize:vertical;min-height:60px;margin-bottom:10px;background:#111;color:#ccc;border:1px solid #2a2a2a"
oninput="_designerContext = this.value">${escHtml(_designerContext)}</textarea>`;
// Specific Context inputs / guidelines
const activeFn = (lbIdx >= 0 && lbNames[lbIdx]) ? lbNames[lbIdx] : null;
let seedBtns = [];
if (activeFn && filePrompts[activeFn]) {
seedBtns.push(`<button class="sb-btn" style="font-size:9px;padding:1px 5px;margin:0;height:16px;line-height:1;background:#f59e0b;color:#111" onclick="designerSeedFromActive()" title="Seed the input with the active image's generation prompt">Seed from Active</button>`);
}
if (_designerContext) {
seedBtns.push(`<button class="sb-btn" style="font-size:9px;padding:1px 5px;margin:0;height:16px;line-height:1;background:#333;color:#ccc" onclick="designerClearContext()" title="Clear guidelines">Clear</button>`);
}
const buttonsHtml = seedBtns.length ? `<div style="display:flex;gap:4px;margin-left:auto">${seedBtns.join('')}</div>` : '';
html += `<div style="display:flex;align-items:center;margin-bottom:4px">
<div class="sb-label" style="margin:0">Context / Guidelines</div>
${buttonsHtml}
</div>
<div class="sb-prompt-wrap" style="position:relative;margin-bottom:10px">
<textarea id="designerContextInput" class="sb-input" rows="4" autocomplete="off"
placeholder="e.g. standing pose, legs spread, hands bound behind back, explicit photography..."
style="resize:vertical;min-height:60px;margin-bottom:0;background:#111;color:#ccc;border:1px solid #2a2a2a"
oninput="_designerContext = this.value;showPromptSuggest(this,'designerPromptSuggest')"
onfocus="showPromptSuggest(this,'designerPromptSuggest')"
onblur="setTimeout(()=>hidePromptSuggest('designerPromptSuggest'),150)"
onkeydown="if(event.key==='Escape')hidePromptSuggest('designerPromptSuggest')">${escHtml(_designerContext)}</textarea>
<div id="designerPromptSuggest" style="display:none;position:absolute;left:0;right:0;z-index:50;max-height:180px;overflow-y:auto;background:#18181b;border:1px solid #2a2a2a;border-radius:6px;margin-top:2px;box-shadow:0 6px 20px rgba(0,0,0,0.6)"></div>
</div>`;
// Checkbox for active image context
const activeFn = (lbIdx >= 0 && lbNames[lbIdx]) ? lbNames[lbIdx] : null;
if (activeFn) {
const checkedAttr = _designerUseActiveImg ? ' checked' : '';
html += `<label style="display:flex;align-items:center;gap:6px;font-size:11px;color:#ccc;margin-bottom:12px;cursor:pointer">
@@ -14435,10 +14808,21 @@
html += `<div style="font-size:10px;color:#555;margin-bottom:12px">No active image selected to extract context.</div>`;
}
// Generate Button
html += `<button class="sb-btn amber" id="designerGenerateBtn" onclick="submitDesignerGenerate()" style="width:100%;margin-bottom:15px">
Generate Poses
</button>`;
// Generate or Follow up Buttons
if (_designerHistory && _designerHistory.length > 0) {
html += `<div style="display:flex;gap:4px;margin-bottom:15px">
<button class="sb-btn amber" id="designerGenerateBtn" onclick="submitDesignerGenerate()" style="flex:1;margin:0">
Follow up Poses
</button>
<button class="sb-btn" onclick="designerResetSession()" style="background:#333;color:#ccc;margin:0" title="Start a fresh design session (clears chat history)">
Reset
</button>
</div>`;
} else {
html += `<button class="sb-btn amber" id="designerGenerateBtn" onclick="submitDesignerGenerate()" style="width:100%;margin-bottom:15px">
Generate Poses
</button>`;
}
// Results Container
html += `<div id="designerResultsContainer">`;
@@ -14450,12 +14834,12 @@
const divId = 'designer_pose_' + name.replace(/\s+/g, '_');
html += `
<div class="sb-pose-edit" id="${divId}" style="border:1px solid #2a2a2a;background:#141414;border-radius:6px;padding:8px;margin-bottom:10px">
<div style="font-weight:bold;color:#f59e0b;font-size:11px;margin-bottom:4px">${nameEsc}</div>
<div class="sb-pose-name" style="font-weight:bold;color:#f59e0b;font-size:11px;margin-bottom:4px">${nameEsc}</div>
<textarea class="sb-input sb-pose-text" rows="5" style="font-size:10px;background:#1a1a1a;color:#ccc;border:1px solid #333;margin-bottom:6px;resize:vertical;width:100%">${bodyEsc}</textarea>
<div style="display:flex;gap:4px;flex-wrap:wrap">
<button class="sb-btn" style="font-size:9px;padding:2px 6px;margin:0" onclick="designerLoadToCustomPrompt('${nameEsc}', this)" title="Copy this pose prompt into the custom prompt box">Copy to Custom</button>
<button class="sb-btn teal" style="font-size:9px;padding:2px 6px;margin:0" onclick="designerSaveToLibrary('${nameEsc}', this)" title="Add this pose permanently to the poses.md library">Save to Library</button>
<button class="sb-btn purple" style="font-size:9px;padding:2px 6px;margin:0" onclick="designerLoadAndSwitch('${nameEsc}', this)" title="Copy prompt and switch to Generate tab instantly">Use & Switch</button>
<button class="sb-btn" style="font-size:9px;padding:2px 6px;margin:0" onclick="designerLoadToCustomPrompt(this)" title="Copy this pose prompt into the custom prompt box">Copy to Custom</button>
<button class="sb-btn teal" style="font-size:9px;padding:2px 6px;margin:0" onclick="designerSaveToLibrary(this)" title="Add this pose permanently to the poses.md library">Save to Library</button>
<button class="sb-btn purple" style="font-size:9px;padding:2px 6px;margin:0" onclick="designerLoadAndSwitch(this)" title="Copy prompt and switch to Generate tab instantly">Use & Switch</button>
</div>
</div>`;
});
@@ -14468,6 +14852,38 @@
panel.scrollTop = savedScrollTop;
}
function designerSeedFromActive() {
const activeFn = (lbIdx >= 0 && lbNames[lbIdx]) ? lbNames[lbIdx] : null;
if (activeFn && filePrompts[activeFn]) {
_designerContext = filePrompts[activeFn];
const input = document.getElementById('designerContextInput');
if (input) {
input.value = _designerContext;
}
renderSidebarDesigner();
showToast('Seeded designer with active image prompt', 'success');
} else {
showToast('No active image prompt available', 'error');
}
}
function designerClearContext() {
_designerContext = "";
const input = document.getElementById('designerContextInput');
if (input) {
input.value = "";
}
renderSidebarDesigner();
}
function designerResetSession() {
_designerHistory = [];
_designerResults = null;
_designerContext = "";
renderSidebarDesigner();
showToast('Designer session reset', 'info');
}
async function submitDesignerGenerate() {
const btn = document.getElementById('designerGenerateBtn');
if (!btn) return;
@@ -14476,10 +14892,16 @@
btn.innerHTML = '<span class="sb-spinner" style="width:10px;height:10px;border-width:2px;margin:0"></span> Designing Poses…';
const activeFn = (lbIdx >= 0 && lbNames[lbIdx]) ? lbNames[lbIdx] : null;
if (_designerContext && _designerContext.trim()) {
savePromptHistory(_designerContext, 'designerPromptHistory');
}
const payload = {
n: _designerNum,
context: _designerContext,
filename: _designerUseActiveImg ? activeFn : null
filename: _designerUseActiveImg ? activeFn : null,
messages: _designerHistory.length > 0 ? _designerHistory : null
};
try {
@@ -14490,7 +14912,17 @@
});
if (r.ok) {
const data = await r.json();
// Build/update history
if (_designerHistory.length === 0) {
_designerHistory.push({ "role": "user", "content": _designerContext || "Generate poses based on active image features." });
} else if (_designerContext) {
_designerHistory.push({ "role": "user", "content": _designerContext });
}
_designerHistory.push({ "role": "assistant", "content": data.raw });
_designerResults = data.poses;
_designerContext = ""; // clear after generation
showToast(`Successfully designed ${Object.keys(data.poses).length} custom poses`, 'success');
} else {
showToast('Designer failed: ' + await r.text(), 'error');
@@ -14504,9 +14936,11 @@
}
}
function designerLoadToCustomPrompt(name, btn) {
function designerLoadToCustomPrompt(btn) {
const box = btn.closest('.sb-pose-edit');
if (!box) return;
const nameEl = box.querySelector('.sb-pose-name');
const name = nameEl ? nameEl.textContent.trim() : 'Designed Pose';
const textarea = box.querySelector('.sb-pose-text');
if (!textarea) return;
const promptVal = textarea.value.trim();
@@ -14522,9 +14956,15 @@
}
}
async function designerSaveToLibrary(name, btn) {
async function designerSaveToLibrary(btn) {
const box = btn.closest('.sb-pose-edit');
if (!box) return;
const nameEl = box.querySelector('.sb-pose-name');
const name = nameEl ? nameEl.textContent.trim() : '';
if (!name) {
showToast('Pose name not found.', 'error');
return;
}
const textarea = box.querySelector('.sb-pose-text');
if (!textarea) return;
const promptVal = textarea.value.trim();
@@ -14562,8 +15002,8 @@
}
}
function designerLoadAndSwitch(name, btn) {
designerLoadToCustomPrompt(name, btn);
function designerLoadAndSwitch(btn) {
designerLoadToCustomPrompt(btn);
switchSidebarTab('generate');
}
</script>