updates UI

This commit is contained in:
mike
2026-07-01 06:43:29 +02:00
parent 94419d7673
commit ec4fa1aa72
3 changed files with 211 additions and 23 deletions

View File

@@ -2146,6 +2146,12 @@
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>Path
</button>
<button class="sb-btn" onclick="copyPageUrl()" style="padding:2px 6px;font-size:9px;min-width:auto;display:inline-flex;align-items:center;gap:3px;height:20px;border-radius:4px" title="Copy current page URL to clipboard">
<svg width="8" height="8" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>URL
</button>
</div>
<span id="lbPoseTag" style="font-size:11px;color:#f59e0b;display:none"></span>
@@ -2379,6 +2385,12 @@
<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 Path
</button>
<button class="sb-btn" onclick="copyPageUrl()" style="padding:2px 6px;font-size:9px;min-width:auto;display:inline-flex;align-items:center;gap:4px" title="Copy current page URL 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 URL
</button>
</div>
</div>
<input type="text" class="sb-input" id="lbNameInput"
@@ -2544,6 +2556,34 @@
// --- HYDRATION_START ---
const PRELOADED_IMAGES = [
"20260701_064213_sc_pad_view_000_000deg.png",
"20260701_063422_sc_2_20260618_052526_image.nobg.nobg.png",
"20260701_062850_sc_pad_view_000_000deg.png",
"20260701_062218_pad_view_000_000deg.png",
"20260701_062201_pad_view_000_000deg.png",
"20260701_062144_pad_view_000_000deg.png",
"20260701_062128_pad_view_000_000deg.png",
"20260701_062111_pad_view_000_000deg.png",
"20260701_062054_pad_view_000_000deg.png",
"20260701_062037_pad_view_000_000deg.png",
"20260701_061913_pad_view_000_000deg.png",
"20260701_061854_pad_view_000_000deg.png",
"20260701_061837_pad_view_000_000deg.png",
"20260701_061814_pad_view_000_000deg.png",
"20260701_060854_pad_view_000_000deg.png",
"20260701_060215_pad_view_000_000deg.png",
"20260701_060110_pad_view_000_000deg.png",
"20260701_060054_pad_view_000_000deg.png",
"20260701_055944_pad_view_000_000deg.png",
"20260701_055727_pad_view_000_000deg.png",
"20260701_055710_pad_view_000_000deg.png",
"20260701_055654_pad_view_000_000deg.png",
"20260701_055637_pad_view_000_000deg.png",
"20260701_055621_pad_view_000_000deg.png",
"20260701_055605_pad_view_000_000deg.png",
"20260701_055542_pad_view_000_000deg.png",
"up_f475c600_face.png",
"20260701_055451_pad_view_000_000deg.png",
"20260701_051754_image.png",
"20260701_051737_image.png",
"20260701_051721_image.png",
@@ -3270,7 +3310,6 @@
"_turntable/cg_d032c1ef/views/view_002_030deg.png",
"20260618_054230_image.png_face.png",
"cg_f1e85987_face.png",
"up_f475c600_face.png",
"20260618_174516_image.png_face.png",
"up_76ef7e4f_face.png",
"cg_e79533a4_face.png",
@@ -4685,8 +4724,8 @@
"_turntable/cg_7ec17537/views/view_003_045deg.png",
"_turntable/cg_7ec17537/views/view_002_030deg.png",
"_turntable/cg_7ec17537/views/view_001_015deg.png",
"_turntable/cg_7ec17537/views/view_000_000deg.png",
"_turntable/cg_7ec17537/views/20260628_033600_dup_view_000_000deg.png",
"_turntable/cg_7ec17537/views/view_000_000deg.png",
"_turntable/cg_7ec17537/views/20260628_033831_dup_20260628_033600_dup_view_000_000deg.png",
"_turntable/up_154b502c/views/view_023_345deg.png",
"_turntable/up_154b502c/views/view_022_330deg.png",
@@ -8078,6 +8117,105 @@
let _sbPadOutpaint = false;
let _sbPadUniform = false;
// ---- scenery & art-form state ----
let _sbGenSceneryType = 'black_void'; // 'black_void', 'inherit_empty', 'inherit_explicit', 'custom'
let _sbGenSceneryCustomVal = '';
let _sbGenArtFormType = 'default'; // 'default', 'art1', 'art2', 'custom'
let _sbGenArtFormCustomVal = '';
function cleanPosePrompt(p) {
if (!p) return "";
let items = [];
p.split('\n').forEach(line => {
const parts = line.split(/(?<=[.!?])\s+/).map(s => s.trim()).filter(Boolean);
items.push(...parts);
});
if (items.length === 0) return "";
// Check if first line/sentence matches any of our known sceneries (case-insensitive, strip punctuation)
const firstItem = items[0].toLowerCase().replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"").trim();
if (firstItem === "you are in a black void" || firstItem === "you are in black void" || firstItem === "inherit scene from reference image") {
items.shift();
}
if (items.length === 0) return "";
// Check if last line/sentence matches any of our known art-forms (case-insensitive, strip punctuation)
const lastItem = items[items.length - 1].toLowerCase().replace(/[.,\/#!$%\^&\*;:{}=\-_`~()]/g,"").trim();
const knownArtForms = [
"anatomically precise hyperrealistic high detail keep the characteristics of the reference image",
"perfect anatomy photo-realistic detailled identical person from reference image",
"perfect anatomy photo-realistic detailed identical person",
"photo-realistic keep identical person from reference"
];
if (knownArtForms.some(art => lastItem.includes(art) || art.includes(lastItem))) {
items.pop();
}
return items.join('\n');
}
function getCompoundPrompt(rawPrompt) {
const core = cleanPosePrompt(rawPrompt).trim();
if (!core) return "";
// 1. Get Scenery
let scenery = "";
if (_sbGenSceneryType === 'black_void') {
scenery = "you are in a black void.";
} else if (_sbGenSceneryType === 'inherit_explicit') {
scenery = "inherit scene from reference image.";
} else if (_sbGenSceneryType === 'custom') {
scenery = _sbGenSceneryCustomVal.trim();
if (scenery && !/[.!?]$/.test(scenery)) scenery += ".";
}
// 2. Get Art-form
let artForm = "";
if (_sbGenArtFormType === 'default') {
artForm = "photo-realistic keep identical person from reference";
} else if (_sbGenArtFormType === 'art1') {
artForm = "Anatomically precise, hyperrealistic, high detail, keep the characteristics of the reference image.";
} else if (_sbGenArtFormType === 'art2') {
artForm = "Perfect anatomy, photo-realistic, detailed, identical person from reference image";
} else if (_sbGenArtFormType === 'custom') {
artForm = _sbGenArtFormCustomVal.trim();
}
// Combine them
let parts = [];
if (scenery) parts.push(scenery);
parts.push(core);
if (artForm) parts.push(artForm);
return parts.join('\n');
}
function onSbSceneryChange(val) {
_sbGenSceneryType = val;
const el = document.getElementById('sbGenSceneryCustom');
if (el) {
el.style.display = val === 'custom' ? 'block' : 'none';
}
updateSbGenBtn();
}
function onSbSceneryCustomInput(val) {
_sbGenSceneryCustomVal = val;
updateSbGenBtn();
}
function onSbArtFormChange(val) {
_sbGenArtFormType = val;
const el = document.getElementById('sbGenArtFormCustom');
if (el) {
el.style.display = val === 'custom' ? 'block' : 'none';
}
updateSbGenBtn();
}
function onSbArtFormCustomInput(val) {
_sbGenArtFormCustomVal = val;
updateSbGenBtn();
}
const CAMERA_ANGLES = [
// Absolute camera positions
{ name: 'Front', icon: '⊙', prompt: 'Head-on straight-on full-body portrait, frontal camera, realistic, transparent background', relative: false },
@@ -11101,6 +11239,15 @@
});
}
function copyPageUrl() {
const url = window.location.href;
navigator.clipboard.writeText(url).then(() => {
showToast('Page URL copied to clipboard', 'success');
}).catch(err => {
showToast('Failed to copy: ' + err, 'error');
});
}
async function lbRemoveBg() {
if (!_backendOnline) {
showToast("Cannot remove background: Backend API is offline", "error");
@@ -13815,6 +13962,17 @@
if (el) _sbPoseEdits[name] = el.value;
});
// Preserve decorated prompt custom inputs
const prevSceneryType = document.getElementById('sbGenScenerySelect')?.value ?? _sbGenSceneryType;
const prevSceneryCustom = document.getElementById('sbGenSceneryCustom')?.value ?? _sbGenSceneryCustomVal;
const prevArtFormType = document.getElementById('sbGenArtFormSelect')?.value ?? _sbGenArtFormType;
const prevArtFormCustom = document.getElementById('sbGenArtFormCustom')?.value ?? _sbGenArtFormCustomVal;
_sbGenSceneryType = prevSceneryType;
_sbGenSceneryCustomVal = prevSceneryCustom;
_sbGenArtFormType = prevArtFormType;
_sbGenArtFormCustomVal = prevArtFormCustom;
const donePoses = new Set();
lbNames.forEach(n => { if (filePoses[n]) donePoses.add(filePoses[n]); });
@@ -13886,15 +14044,39 @@
onclick="refineSbPrompt()" title="AI prompt refinement"> Refine</button>
</div>
</div>
<div class="sb-prompt-wrap" style="position:relative;margin-bottom:10px">
<textarea class="sb-input" id="sbGenPromptInput" rows="2" autocomplete="off"
placeholder="Prompt — overrides pose"
oninput="updateSbGenBtn();autoGrowPrompt(this);showPromptSuggest(this)"
onfocus="autoGrowPrompt(this);showPromptSuggest(this)"
onblur="setTimeout(()=>hidePromptSuggest(),150)"
onkeydown="if(event.key==='Escape')hidePromptSuggest()"
style="resize:none;max-height:320px;overflow-y:auto;line-height:1.4;font-family:inherit">${escHtml(prevPromptVal)}</textarea>
<div id="sbPromptSuggest" 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 style="background:rgba(255,255,255,0.02);border:1px solid #222;border-radius:6px;padding:8px;margin-bottom:10px">
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px">
<span style="font-size:10px;color:#888;font-weight:600;text-transform:uppercase;letter-spacing:0.5px">Scenery</span>
<select id="sbGenScenerySelect" onchange="onSbSceneryChange(this.value)" style="background:#111;border:1px solid #2a2a2a;border-radius:4px;color:#ccc;font-size:11px;padding:2px 4px;cursor:pointer;width:150px">
<option value="black_void" ${_sbGenSceneryType==='black_void'?'selected':''}>Black void</option>
<option value="inherit_empty" ${_sbGenSceneryType==='inherit_empty'?'selected':''}>Inherit (empty)</option>
<option value="inherit_explicit" ${_sbGenSceneryType==='inherit_explicit'?'selected':''}>Explicit inherit</option>
<option value="custom" ${_sbGenSceneryType==='custom'?'selected':''}>Custom scenery...</option>
</select>
</div>
<input type="text" id="sbGenSceneryCustom" placeholder="Type custom scenery description..." value="${escHtml(_sbGenSceneryCustomVal)}" style="display:${_sbGenSceneryType==='custom'?'block':'none'};width:100%;box-sizing:border-box;background:#111;border:1px solid #2a2a2a;border-radius:4px;color:#ccc;font-size:11px;padding:4px 6px;margin-bottom:6px" oninput="onSbSceneryCustomInput(this.value)">
<div class="sb-prompt-wrap" style="position:relative;margin-bottom:6px">
<textarea class="sb-input" id="sbGenPromptInput" rows="2" autocomplete="off"
placeholder="Pose or actions prompt (e.g. standing, hands on hips)"
oninput="updateSbGenBtn();autoGrowPrompt(this);showPromptSuggest(this)"
onfocus="autoGrowPrompt(this);showPromptSuggest(this)"
onblur="setTimeout(()=>hidePromptSuggest(),150)"
onkeydown="if(event.key==='Escape')hidePromptSuggest()"
style="resize:none;max-height:320px;overflow-y:auto;line-height:1.4;font-family:inherit;margin-bottom:0">${escHtml(prevPromptVal)}</textarea>
<div id="sbPromptSuggest" 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>
<div style="display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:6px">
<span style="font-size:10px;color:#888;font-weight:600;text-transform:uppercase;letter-spacing:0.5px">Art-form</span>
<select id="sbGenArtFormSelect" onchange="onSbArtFormChange(this.value)" style="background:#111;border:1px solid #2a2a2a;border-radius:4px;color:#ccc;font-size:11px;padding:2px 4px;cursor:pointer;width:150px">
<option value="default" ${_sbGenArtFormType==='default'?'selected':''}>Realistic Portrait (default)</option>
<option value="art1" ${_sbGenArtFormType==='art1'?'selected':''}>Anatomically precise (high detail)</option>
<option value="art2" ${_sbGenArtFormType==='art2'?'selected':''}>Perfect anatomy (photo-realistic)</option>
<option value="custom" ${_sbGenArtFormType==='custom'?'selected':''}>Custom art-form...</option>
</select>
</div>
<input type="text" id="sbGenArtFormCustom" placeholder="Type custom art-form style..." value="${escHtml(_sbGenArtFormCustomVal)}" style="display:${_sbGenArtFormType==='custom'?'block':'none'};width:100%;box-sizing:border-box;background:#111;border:1px solid #2a2a2a;border-radius:4px;color:#ccc;font-size:11px;padding:4px 6px;margin-top:6px" oninput="onSbArtFormCustomInput(this.value)">
</div>`;
html += '<div class="sb-sep"></div><div class="sb-label">Poses</div>';
@@ -14474,7 +14656,8 @@
// Use edited text if available, else original
const edited = _sbPoseEdits[name];
const orig = availablePoses[name]?.text ?? availablePoses[name] ?? name;
prompts.push((edited !== undefined && edited.trim()) ? edited.trim() : String(orig));
const rawPose = (edited !== undefined && edited.trim()) ? edited.trim() : String(orig);
prompts.push(getCompoundPrompt(rawPose));
});
// Camera angles — treated as prompts with no pose tag
_sbSelectedAngles.forEach(name => {
@@ -14483,7 +14666,7 @@
// shows a green ✓ once generated, mirroring pose tracking.
if (ang) { prompts.push(ang.prompt); poses.push(name); }
});
if (promptVal) { prompts.push(promptVal); poses.push(null); savePromptHistory(promptVal); }
if (promptVal) { prompts.push(getCompoundPrompt(promptVal)); poses.push(null); savePromptHistory(promptVal); }
else if (_sbPadOutpaint) {
const isZero = (v) => !v || v === '0' || v === '0%' || v === '0px';
if (!isZero(_sbPadTop) || !isZero(_sbPadRight) || !isZero(_sbPadBottom) || !isZero(_sbPadLeft)) {