This commit is contained in:
mike
2026-06-28 05:48:36 +02:00
parent 419f732cf6
commit 3386ec9e84

View File

@@ -2305,8 +2305,22 @@
// --- HYDRATION_START --- // --- HYDRATION_START ---
const PRELOADED_IMAGES = [ const PRELOADED_IMAGES = [
"_turntable/cg_7ec17537/views/20260628_054328_sc_dup_20260628_033600_dup_view_000_000deg.png",
"_turntable/cg_7ec17537/views/20260628_053717_sc_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_053620_sc_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_053533_sc_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_053337_sc_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png",
"20260628_053000_image.png",
"_turntable/cg_7ec17537/views/20260628_052745_sc_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_052516_sc_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_052422_sc_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_052318_sc_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_052216_sc_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_052123_sc_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_051801_sc_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_050826_sc_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_050349_sc_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png",
"20260628_045734_image.png", "20260628_045734_image.png",
"_turntable/cg_7ec17537/views/20260628_045628_sc_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_044252_sc_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png", "_turntable/cg_7ec17537/views/20260628_044252_sc_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_043545_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png", "_turntable/cg_7ec17537/views/20260628_043545_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.png",
"_turntable/cg_7ec17537/views/20260628_043148_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png", "_turntable/cg_7ec17537/views/20260628_043148_pad_20260628_035257_dup_20260628_033600_dup_view_000_000deg.nobg.nobg.nobg.png",
@@ -3477,8 +3491,8 @@
"_turntable/cg_7ec17537/views/view_002_030deg.png", "_turntable/cg_7ec17537/views/view_002_030deg.png",
"_turntable/cg_7ec17537/views/view_001_015deg.png", "_turntable/cg_7ec17537/views/view_001_015deg.png",
"_turntable/cg_7ec17537/views/view_000_000deg.png", "_turntable/cg_7ec17537/views/view_000_000deg.png",
"_turntable/cg_7ec17537/views/20260628_033600_dup_view_000_000deg.png",
"_turntable/cg_7ec17537/views/20260628_033831_dup_20260628_033600_dup_view_000_000deg.png", "_turntable/cg_7ec17537/views/20260628_033831_dup_20260628_033600_dup_view_000_000deg.png",
"_turntable/cg_7ec17537/views/20260628_033600_dup_view_000_000deg.png",
"_turntable/up_154b502c/views/view_023_345deg.png", "_turntable/up_154b502c/views/view_023_345deg.png",
"_turntable/up_154b502c/views/view_022_330deg.png", "_turntable/up_154b502c/views/view_022_330deg.png",
"_turntable/up_154b502c/views/view_021_315deg.png", "_turntable/up_154b502c/views/view_021_315deg.png",
@@ -4133,8 +4147,8 @@
"20260626_234431_crop_20260626_231655_sc_image.nobg.png", "20260626_234431_crop_20260626_231655_sc_image.nobg.png",
"20260626_231655_sc_image.nobg.png", "20260626_231655_sc_image.nobg.png",
"20260618_181600_image.png_face.png", "20260618_181600_image.png_face.png",
"20260626_231655_sc_image.png",
"20260626_234334_dup_20260626_231655_sc_image.png", "20260626_234334_dup_20260626_231655_sc_image.png",
"20260626_231655_sc_image.png",
"20260626_231349_sc_image.png", "20260626_231349_sc_image.png",
"20260626_231127_image.png", "20260626_231127_image.png",
"_turntable/up_624995c3/views/view_011_330deg.png", "_turntable/up_624995c3/views/view_011_330deg.png",
@@ -4932,8 +4946,8 @@
"20260625_024438_dup_20260624_165109_0_20260622_091731_image.nobg.png", "20260625_024438_dup_20260624_165109_0_20260622_091731_image.nobg.png",
"20260625_023536_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.nobg.png",
"20260624_165109_0_20260622_091731_image.png",
"20260625_023536_dup_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", "up_ab858b0a_face.png",
"20260625_022332_dup_20260625_022250_sc_image.png", "20260625_022332_dup_20260625_022250_sc_image.png",
"20260625_022250_sc_image.png", "20260625_022250_sc_image.png",
@@ -5241,8 +5255,8 @@
"20260624_152038_Screenshot_From_2026-06-17_07-41-27.png", "20260624_152038_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_152027_Screenshot_From_2026-06-17_07-41-27.png", "20260624_152027_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_143523_Screenshot_From_2026-06-17_07-41-27.nobg.png", "20260624_143523_Screenshot_From_2026-06-17_07-41-27.nobg.png",
"20260624_143523_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_151746_dup_20260624_143523_Screenshot_From_2026-06-17_07-41-27.png", "20260624_151746_dup_20260624_143523_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_143523_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_151501_dup_20260624_143523_Screenshot_From_2026-06-17_07-41-27.png", "20260624_151501_dup_20260624_143523_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_151439_dup_20260624_143523_Screenshot_From_2026-06-17_07-41-27.png", "20260624_151439_dup_20260624_143523_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_151250_Screenshot_From_2026-06-17_07-41-27.nobg.png", "20260624_151250_Screenshot_From_2026-06-17_07-41-27.nobg.png",
@@ -5258,8 +5272,8 @@
"20260624_143700_Screenshot_From_2026-06-17_07-41-27.png", "20260624_143700_Screenshot_From_2026-06-17_07-41-27.png",
"20260624_143647_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_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_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_143024_mr_image.png",
"20260624_142959_mr_image.png", "20260624_142959_mr_image.png",
"20260624_142923_mr_image.png", "20260624_142923_mr_image.png",
@@ -6839,8 +6853,8 @@
"t159zr-1.png", "t159zr-1.png",
"kbk99v.png", "kbk99v.png",
"kk563t.png", "kk563t.png",
"20260626_003137_dup_20260626_003125_dup_Pasted image (9).png",
"Pasted image (9).png", "Pasted image (9).png",
"20260626_003137_dup_20260626_003125_dup_Pasted image (9).png",
"Pasted image (4).png", "Pasted image (4).png",
"Pasted image (3).png", "Pasted image (3).png",
"Pasted image.png", "Pasted image.png",
@@ -10984,8 +10998,15 @@
// Custom prompt field - positioned above poses and under wireframe // Custom prompt field - positioned above poses and under wireframe
html += `<div class="sb-label" style="display:flex;justify-content:space-between;align-items:center"> html += `<div class="sb-label" style="display:flex;justify-content:space-between;align-items:center">
<span>Custom prompt</span> <span>Custom prompt</span>
<button class="sb-btn" id="sbRefineBtn" style="font-size:9px;padding:2px 6px;margin:0" <div style="display:flex;align-items:center;gap:4px">
onclick="refineSbPrompt()" title="AI prompt refinement">✨ Refine</button> <select id="sbLoadPoseSelect" onchange="sbLoadPoseToCustomPrompt(this.value)"
style="background:#111;border:1px solid #2a2a2a;border-radius:4px;color:#aaa;font-size:10px;padding:2px 4px;max-width:130px;cursor:pointer">
<option value="">— Load pose —</option>
${Object.keys(availablePoses).sort((a, b) => a.localeCompare(b, undefined, {sensitivity: 'base'})).map(name => `<option value="${escHtml(name)}">${escHtml(name)}</option>`).join('')}
</select>
<button class="sb-btn" id="sbRefineBtn" style="font-size:9px;padding:2px 6px;margin:0"
onclick="refineSbPrompt()" title="AI prompt refinement"> Refine</button>
</div>
</div> </div>
<div class="sb-prompt-wrap" style="position:relative;margin-bottom:10px"> <div class="sb-prompt-wrap" style="position:relative;margin-bottom:10px">
<textarea class="sb-input" id="sbGenPromptInput" rows="2" autocomplete="off" <textarea class="sb-input" id="sbGenPromptInput" rows="2" autocomplete="off"
@@ -11035,6 +11056,7 @@
<div style="display:flex;align-items:center;gap:6px;margin-bottom:3px"> <div style="display:flex;align-items:center;gap:6px;margin-bottom:3px">
<span style="font-size:11px;color:#888;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(name)}</span> <span style="font-size:11px;color:#888;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${escHtml(name)}</span>
<label style="font-size:10px;color:#777;display:flex;align-items:center;gap:3px;cursor:pointer"><input type="checkbox" class="sb-pose-beta" ${isBeta?'checked':''}>beta</label> <label style="font-size:10px;color:#777;display:flex;align-items:center;gap:3px;cursor:pointer"><input type="checkbox" class="sb-pose-beta" ${isBeta?'checked':''}>beta</label>
<button class="sb-btn" style="padding:2px 8px;font-size:10px;background:#1e1b4b;color:#a5b4fc;border-color:#312e81" onclick="refinePosePrompt(this)" title="Refine pose prompt via LLM">✨ Refine</button>
<button class="sb-btn" style="padding:2px 8px;font-size:10px" onclick="savePoseToServer(this)">Save</button> <button class="sb-btn" style="padding:2px 8px;font-size:10px" onclick="savePoseToServer(this)">Save</button>
<button class="sb-btn danger" style="padding:2px 8px;font-size:10px" onclick="deletePoseFromServer(this)">Delete</button> <button class="sb-btn danger" style="padding:2px 8px;font-size:10px" onclick="deletePoseFromServer(this)">Delete</button>
</div> </div>
@@ -11446,6 +11468,63 @@
} }
} }
function sbLoadPoseToCustomPrompt(poseName) {
if (!poseName) return;
const el = document.getElementById('sbGenPromptInput');
if (!el) return;
const poseText = availablePoses[poseName]?.text ?? availablePoses[poseName];
if (poseText) {
el.value = String(poseText).trim();
autoGrowPrompt(el);
updateSbGenBtn();
showToast(`Pose "${poseName}" loaded into custom prompt`, 'success');
}
// Reset dropdown select back to default
const select = document.getElementById('sbLoadPoseSelect');
if (select) select.value = "";
}
async function refinePosePrompt(btn) {
const box = btn.closest('.sb-pose-edit');
if (!box) return;
const name = box.dataset.pose;
const textarea = box.querySelector('.sb-pose-text');
if (!textarea) return;
let prompt = textarea.value.trim();
if (!prompt) {
showToast('No prompt to refine.', 'info');
return;
}
const oldHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="sb-spinner" style="width:10px;height:10px;border-width:2px;margin:0"></span> Refining…';
try {
const r = await fetch(`${API}/refine-prompt`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt })
});
if (r.ok) {
const { refined } = await r.json();
textarea.value = refined;
_sbPoseEdits[name] = refined;
textarea.style.height = 'auto';
textarea.style.height = textarea.scrollHeight + 'px';
updateSbGenBtn();
showToast(`Pose "${name}" refined`, 'success');
} else {
showToast('Refinement failed: ' + await r.text(), 'error');
}
} catch (e) {
showToast('Refinement error: ' + e, 'error');
} finally {
btn.disabled = false;
btn.innerHTML = oldHtml;
}
}
async function submitSbGenerate() { async function submitSbGenerate() {
if (!_fsModelFilename) return; if (!_fsModelFilename) return;
const promptVal = (document.getElementById('sbGenPromptInput')?.value || '').trim(); const promptVal = (document.getElementById('sbGenPromptInput')?.value || '').trim();
@@ -12146,7 +12225,18 @@
const canGen = !!bgRef && !!person && person.kind === 'file' const canGen = !!bgRef && !!person && person.kind === 'file'
&& (!_sceneRefs[2] || _sceneRefs[2].kind === 'file'); && (!_sceneRefs[2] || _sceneRefs[2].kind === 'file');
html += `<div class="sb-sep"></div> html += `<div class="sb-sep"></div>
<div class="sb-label">Prompt (optional) <span style="color:#555;font-weight:400;font-size:9px">· history while typing</span></div> <div class="sb-label" style="display:flex;justify-content:space-between;align-items:center">
<span>Prompt (optional) <span style="color:#555;font-weight:400;font-size:9px">· history</span></span>
<div style="display:flex;align-items:center;gap:4px">
<select id="sceneLoadPoseSelect" onchange="sceneLoadPoseToPrompt(this.value)"
style="background:#111;border:1px solid #2a2a2a;border-radius:4px;color:#aaa;font-size:10px;padding:2px 4px;max-width:130px;cursor:pointer">
<option value="">— Load pose —</option>
${Object.keys(availablePoses).sort((a, b) => a.localeCompare(b, undefined, {sensitivity: 'base'})).map(name => `<option value="${escHtml(name)}">${escHtml(name)}</option>`).join('')}
</select>
<button class="sb-btn" id="sceneRefineBtn" style="font-size:9px;padding:2px 6px;margin:0"
onclick="refineScenePrompt()" title="AI prompt refinement"> Refine</button>
</div>
</div>
<div class="sb-prompt-wrap" style="position:relative;margin-bottom:10px"> <div class="sb-prompt-wrap" style="position:relative;margin-bottom:10px">
<textarea class="sb-input" id="scenePromptInput" rows="2" autocomplete="off" <textarea class="sb-input" id="scenePromptInput" rows="2" autocomplete="off"
placeholder="Auto: place person in scene… (type to search history)" placeholder="Auto: place person in scene… (type to search history)"
@@ -12452,6 +12542,64 @@
} }
} }
function sceneLoadPoseToPrompt(poseName) {
if (!poseName) return;
const el = document.getElementById('scenePromptInput');
if (!el) return;
const poseText = availablePoses[poseName]?.text ?? availablePoses[poseName];
if (poseText) {
el.value = String(poseText).trim();
autoGrowPrompt(el);
showToast(`Pose "${poseName}" loaded into scenery prompt`, 'success');
}
// Reset dropdown select back to default
const select = document.getElementById('sceneLoadPoseSelect');
if (select) select.value = "";
}
async function refineScenePrompt() {
const el = document.getElementById('scenePromptInput');
if (!el) return;
let prompt = el.value.trim();
if (!prompt) {
// If empty, use current image's prompt if available
if (lbIdx >= 0 && lbNames[lbIdx]) {
const fn = lbNames[lbIdx];
prompt = filePrompts[fn] || '';
}
}
if (!prompt) {
showToast('No prompt to refine. Enter one or select an image.', 'info');
return;
}
const btn = document.getElementById('sceneRefineBtn');
const oldHtml = btn.innerHTML;
btn.disabled = true;
btn.innerHTML = '<span class="sb-spinner" style="width:10px;height:10px;border-width:2px;margin:0"></span> Refining…';
try {
const r = await fetch(`${API}/refine-prompt`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ prompt })
});
if (r.ok) {
const { refined } = await r.json();
el.value = (el.value.trim() + "\n\n" + refined).trim();
autoGrowPrompt(el);
showToast('Prompt refined', 'success');
} else {
showToast('Refinement failed: ' + await r.text(), 'error');
}
} catch (e) {
showToast('Refinement error: ' + e, 'error');
} finally {
btn.disabled = false;
btn.innerHTML = oldHtml;
}
}
async function submitGenerateScenery() { async function submitGenerateScenery() {
// Map positional slots → backend contract. // Map positional slots → backend contract.
const bg = _sceneRefs[0], person = _sceneRefs[1], extra = _sceneRefs[2]; const bg = _sceneRefs[0], person = _sceneRefs[1], extra = _sceneRefs[2];