dphn/Dolphin3.0-Mistral-24B is the ungated mirror of the Dolphin 3.0 Mistral 24B — exactly what you asked for. It's ~48GB fp16, which needs GPU+CPU split (device_map="auto" with 32GB on GPU, ~16GB in RAM). Let me kick off the download and update the service in parallel.
This commit is contained in:
@@ -54,3 +54,5 @@ generating poses themself should use the adviced dimensions rather than the base
|
|||||||
|
|
||||||
introduce a like/dislike base system for images/group to determine the sort order (and more)
|
introduce a like/dislike base system for images/group to determine the sort order (and more)
|
||||||
- would also dislike the pose
|
- would also dislike the pose
|
||||||
|
|
||||||
|
creating scenery should keep both video-frame + ref images as references, we only see 1 image now.
|
||||||
|
|||||||
@@ -1586,34 +1586,30 @@
|
|||||||
background: rgba(34,197,94,0.9); color: #04210f;
|
background: rgba(34,197,94,0.9); color: #04210f;
|
||||||
border-radius: 5px; padding: 2px 8px; font-size: 11px; font-weight: 600;
|
border-radius: 5px; padding: 2px 8px; font-size: 11px; font-weight: 600;
|
||||||
}
|
}
|
||||||
/* 3-slot reference summary (image1 / image2 / image3) */
|
/* 3 interactive reference slots (image1 / image2 / image3) */
|
||||||
.scene-slots { display: flex; gap: 6px; margin-bottom: 12px; }
|
.scene-slots { display: flex; gap: 6px; margin-bottom: 12px; }
|
||||||
.scene-slot {
|
.scene-slot {
|
||||||
flex: 1; min-width: 0; background: #141414; border: 1px solid #262626;
|
flex: 1; min-width: 0; background: #141414; border: 1px solid #262626;
|
||||||
border-radius: 7px; overflow: hidden; text-align: center;
|
border-radius: 7px; overflow: hidden; text-align: center;
|
||||||
}
|
}
|
||||||
.scene-slot.filled { border-color: #2563eb; }
|
.scene-slot.filled { border-color: #2563eb; cursor: grab; }
|
||||||
.scene-slot.empty { border-style: dashed; }
|
.scene-slot.empty { border-style: dashed; }
|
||||||
|
.scene-slot.drop-hover { border-color: #60a5fa; background: #11203f; }
|
||||||
.scene-slot-thumb {
|
.scene-slot-thumb {
|
||||||
width: 100%; aspect-ratio: 1/1; background: #0c0c0c;
|
width: 100%; aspect-ratio: 1/1; background: #0c0c0c; position: relative;
|
||||||
display: flex; align-items: center; justify-content: center;
|
display: flex; align-items: center; justify-content: center;
|
||||||
}
|
}
|
||||||
.scene-slot-thumb img { width: 100%; height: 100%; object-fit: cover; }
|
.scene-slot-thumb img { width: 100%; height: 100%; object-fit: cover; pointer-events: none; }
|
||||||
.scene-slot-thumb .ph { font-size: 18px; color: #333; }
|
.scene-slot-thumb .ph { font-size: 18px; color: #333; }
|
||||||
|
.scene-slot-x {
|
||||||
|
position: absolute; top: 2px; right: 2px; width: 16px; height: 16px; line-height: 14px;
|
||||||
|
background: rgba(0,0,0,0.7); border: 1px solid #444; border-radius: 4px;
|
||||||
|
color: #ddd; font-size: 10px; cursor: pointer; padding: 0;
|
||||||
|
}
|
||||||
|
.scene-slot-x:hover { background: #b91c1c; color: #fff; }
|
||||||
|
.scene-slot-kind { position: absolute; bottom: 1px; left: 2px; font-size: 10px; }
|
||||||
.scene-slot-cap { font-size: 8.5px; color: #888; padding: 3px 2px 2px; line-height: 1.25; }
|
.scene-slot-cap { font-size: 8.5px; color: #888; padding: 3px 2px 2px; line-height: 1.25; }
|
||||||
.scene-slot-cap b { color: #bbb; display: block; font-size: 9px; }
|
.scene-slot-cap b { color: #bbb; display: block; font-size: 9px; }
|
||||||
/* compact filmstrip picker for image3 */
|
|
||||||
.scene-pick-row { display: flex; gap: 5px; overflow-x: auto; padding-bottom: 4px; }
|
|
||||||
.scene-pick {
|
|
||||||
flex: 0 0 auto; width: 46px; height: 46px; border-radius: 5px; overflow: hidden;
|
|
||||||
border: 2px solid transparent; cursor: pointer; position: relative; background: #0c0c0c;
|
|
||||||
}
|
|
||||||
.scene-pick img { width: 100%; height: 100%; object-fit: cover; pointer-events: none; }
|
|
||||||
.scene-pick.sel { border-color: #2563eb; }
|
|
||||||
.scene-pick .badge3 {
|
|
||||||
position: absolute; bottom: 1px; right: 1px; background: #2563eb; color: #fff;
|
|
||||||
font-size: 9px; font-weight: 700; border-radius: 3px; padding: 0 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===================== SEGMENT TAB ===================== */
|
/* ===================== SEGMENT TAB ===================== */
|
||||||
.segment-checker-hint { font-size: 10px; color: #444; margin-top: 6px; }
|
.segment-checker-hint { font-size: 10px; color: #444; margin-top: 6px; }
|
||||||
@@ -2243,6 +2239,7 @@
|
|||||||
let availableVideos = []; // populated from /videos
|
let availableVideos = []; // populated from /videos
|
||||||
let _fsActiveTab = 'swap';
|
let _fsActiveTab = 'swap';
|
||||||
let _fsSelectedPoses = new Set();
|
let _fsSelectedPoses = new Set();
|
||||||
|
let _sbPoseFilter = ''; // live pose-name filter query (Generate tab)
|
||||||
let _sbSelectedAngles = new Set(); // selected camera angle names
|
let _sbSelectedAngles = new Set(); // selected camera angle names
|
||||||
let _sbRefIndices = []; // ordered filmstrip indices selected as multi-ref (#1, #2, #3)
|
let _sbRefIndices = []; // ordered filmstrip indices selected as multi-ref (#1, #2, #3)
|
||||||
let _sbRefMode = 'combine'; // with 2–3 refs: 'combine' → 1 multi-ref output; 'each' → one edit per ref (cross product)
|
let _sbRefMode = 'combine'; // with 2–3 refs: 'combine' → 1 multi-ref output; 'each' → one edit per ref (cross product)
|
||||||
@@ -2356,7 +2353,12 @@
|
|||||||
function openStudio(gid, startIdx) {
|
function openStudio(gid, startIdx) {
|
||||||
const data = groupData.get(gid);
|
const data = groupData.get(gid);
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
if (lbCurrentGid !== gid) { _sbRefIndices = []; _sceneExtraRef = null; } // clear refs when switching groups
|
if (lbCurrentGid !== gid) { // clear refs when switching groups
|
||||||
|
_sbRefIndices = [];
|
||||||
|
_sceneRefs = [null, null, null];
|
||||||
|
_scenePersonInit = false;
|
||||||
|
_sceneFrameBytes = null;
|
||||||
|
}
|
||||||
lbCurrentGid = gid;
|
lbCurrentGid = gid;
|
||||||
lbUrls = data.urls;
|
lbUrls = data.urls;
|
||||||
lbNames = data.names;
|
lbNames = data.names;
|
||||||
@@ -2427,8 +2429,26 @@
|
|||||||
updateStudio();
|
updateStudio();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reference badge number for filmstrip thumb i (0 = none).
|
||||||
|
// Generate tab → position in _sbRefIndices; Scenery tab → slot index in _sceneRefs.
|
||||||
|
function _filmstripRefBadge(i) {
|
||||||
|
if (_activeSidebarTab === 'scenery') {
|
||||||
|
const name = lbNames[i];
|
||||||
|
const slot = _sceneRefs.findIndex(r => r && r.kind === 'file' && r.name === name);
|
||||||
|
return slot >= 0 ? slot + 1 : 0;
|
||||||
|
}
|
||||||
|
const pos = _sbRefIndices.indexOf(i);
|
||||||
|
return pos >= 0 ? pos + 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
function sbFilmstripClick(event, i) {
|
function sbFilmstripClick(event, i) {
|
||||||
if (event.shiftKey) {
|
if (event.shiftKey) {
|
||||||
|
event.preventDefault();
|
||||||
|
// Scenery tab uses positional slots (fills right→left); Generate uses _sbRefIndices.
|
||||||
|
if (_activeSidebarTab === 'scenery') {
|
||||||
|
sceneAssignRef(lbNames[i]); // re-renders panel + filmstrip
|
||||||
|
return;
|
||||||
|
}
|
||||||
const pos = _sbRefIndices.indexOf(i);
|
const pos = _sbRefIndices.indexOf(i);
|
||||||
if (pos >= 0) {
|
if (pos >= 0) {
|
||||||
_sbRefIndices.splice(pos, 1);
|
_sbRefIndices.splice(pos, 1);
|
||||||
@@ -2439,7 +2459,6 @@
|
|||||||
}
|
}
|
||||||
updateStudio();
|
updateStudio();
|
||||||
if (_activeSidebarTab === 'generate') renderSidebarGenerate();
|
if (_activeSidebarTab === 'generate') renderSidebarGenerate();
|
||||||
event.preventDefault();
|
|
||||||
} else {
|
} else {
|
||||||
lbNavTo(i);
|
lbNavTo(i);
|
||||||
}
|
}
|
||||||
@@ -2545,18 +2564,22 @@
|
|||||||
// 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';
|
||||||
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 = filePoses[n] || '';
|
||||||
const act = i === lbIdx ? ' active' : '';
|
const act = i === lbIdx ? ' active' : '';
|
||||||
const refPos = _sbRefIndices.indexOf(i);
|
// Badge: Generate uses _sbRefIndices order; Scenery uses positional slots.
|
||||||
const refCls = refPos >= 0 ? ' ref-selected' : '';
|
const badge = _filmstripRefBadge(i);
|
||||||
|
const refCls = badge > 0 ? ' ref-selected' : '';
|
||||||
const thumbSrc = isVideo(n) ? posterFor(url) : url;
|
const thumbSrc = isVideo(n) ? posterFor(url) : url;
|
||||||
return '<div class="lb-var-thumb' + act + refCls + '" onclick="sbFilmstripClick(event,' + i + ')" title="Click to view · Shift+Click to add as ref">'
|
// On the Scenery tab the thumbs are draggable onto the slots above.
|
||||||
|
const dragAttr = sceneryActive ? ' draggable="true" ondragstart="sceneFilmDragStart(event,' + i + ')"' : '';
|
||||||
|
return '<div class="lb-var-thumb' + act + refCls + '" onclick="sbFilmstripClick(event,' + i + ')"' + dragAttr + ' title="Click to view · Shift+Click to add as ref">'
|
||||||
+ '<img src="' + thumbSrc + '" loading="lazy" onerror="this.style.opacity=\'0.3\'">'
|
+ '<img src="' + thumbSrc + '" loading="lazy" onerror="this.style.opacity=\'0.3\'">'
|
||||||
+ (isVideo(n) ? '<div class="lb-var-play">▶</div>' : '')
|
+ (isVideo(n) ? '<div class="lb-var-play">▶</div>' : '')
|
||||||
+ (pose ? '<div class="lb-var-pose">' + escHtml(pose) + '</div>' : '')
|
+ (pose ? '<div class="lb-var-pose">' + escHtml(pose) + '</div>' : '')
|
||||||
+ (refPos >= 0 ? '<div class="lb-var-ref-badge">#' + (refPos + 1) + '</div>' : '')
|
+ (badge > 0 ? '<div class="lb-var-ref-badge">#' + badge + '</div>' : '')
|
||||||
+ '</div>';
|
+ '</div>';
|
||||||
}).join('');
|
}).join('');
|
||||||
const active = strip.querySelector('.lb-var-thumb.active');
|
const active = strip.querySelector('.lb-var-thumb.active');
|
||||||
@@ -4522,8 +4545,15 @@
|
|||||||
style="width:48px;height:48px;object-fit:contain;border-radius:4px;border:1px solid #333">
|
style="width:48px;height:48px;object-fit:contain;border-radius:4px;border:1px solid #333">
|
||||||
</div>` : ''}`;
|
</div>` : ''}`;
|
||||||
|
|
||||||
html += '<div class="sb-sep"></div><div class="sb-label">Poses</div><div class="sb-poses-grid" id="sbPosesGrid">';
|
html += '<div class="sb-sep"></div><div class="sb-label">Poses</div>';
|
||||||
if (!availablePoses || Object.keys(availablePoses).length === 0) {
|
const poseCount = availablePoses ? Object.keys(availablePoses).length : 0;
|
||||||
|
if (poseCount > 8) {
|
||||||
|
html += `<input type="text" id="sbPoseFilter" class="sb-input" placeholder="🔍 Filter poses…"
|
||||||
|
value="${escHtml(_sbPoseFilter)}" oninput="sbFilterPoses(this.value)"
|
||||||
|
style="margin-bottom:6px;font-size:11px;padding:5px 8px">`;
|
||||||
|
}
|
||||||
|
html += '<div class="sb-poses-grid" id="sbPosesGrid">';
|
||||||
|
if (!poseCount) {
|
||||||
html += '<div style="font-size:11px;color:#555;padding:6px 0">No poses loaded</div>';
|
html += '<div style="font-size:11px;color:#555;padding:6px 0">No poses loaded</div>';
|
||||||
} else {
|
} else {
|
||||||
html += Object.entries(availablePoses).map(([name, entry]) => {
|
html += Object.entries(availablePoses).map(([name, entry]) => {
|
||||||
@@ -4533,7 +4563,7 @@
|
|||||||
if (entry?.beta) cls += ' beta';
|
if (entry?.beta) cls += ' beta';
|
||||||
const nSafe = name.replace(/'/g, "\\'");
|
const nSafe = name.replace(/'/g, "\\'");
|
||||||
const tip = String(entry?.text ?? entry).replace(/"/g,'"');
|
const tip = String(entry?.text ?? entry).replace(/"/g,'"');
|
||||||
return `<button class="${cls}" onclick="toggleSbPose('${nSafe}')" title="${tip}">${escHtml(name)}</button>`;
|
return `<button class="${cls}" data-pose-name="${escHtml(name.toLowerCase())}" onclick="toggleSbPose('${nSafe}')" title="${tip}">${escHtml(name)}</button>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
html += `</div>`;
|
html += `</div>`;
|
||||||
@@ -4605,13 +4635,13 @@
|
|||||||
}
|
}
|
||||||
html += `<div class="sb-label">Custom prompt</div>
|
html += `<div class="sb-label">Custom prompt</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="1" autocomplete="off"
|
<textarea class="sb-input" id="sbGenPromptInput" rows="2" autocomplete="off"
|
||||||
placeholder="Prompt — overrides pose"
|
placeholder="Prompt — overrides pose"
|
||||||
oninput="updateSbGenBtn();autoGrowPrompt(this);showPromptSuggest(this)"
|
oninput="updateSbGenBtn();autoGrowPrompt(this);showPromptSuggest(this)"
|
||||||
onfocus="autoGrowPrompt(this);showPromptSuggest(this)"
|
onfocus="autoGrowPrompt(this);showPromptSuggest(this)"
|
||||||
onblur="setTimeout(hidePromptSuggest,150)"
|
onblur="setTimeout(()=>hidePromptSuggest(),150)"
|
||||||
onkeydown="if(event.key==='Escape')hidePromptSuggest()"
|
onkeydown="if(event.key==='Escape')hidePromptSuggest()"
|
||||||
style="resize:none;max-height:160px;overflow-y:auto;line-height:1.4;font-family:inherit">${escHtml(prevPromptVal)}</textarea>
|
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 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>
|
||||||
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
|
<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap">
|
||||||
@@ -4626,6 +4656,7 @@
|
|||||||
updateSbGenBtn();
|
updateSbGenBtn();
|
||||||
const promptEl = document.getElementById('sbGenPromptInput');
|
const promptEl = document.getElementById('sbGenPromptInput');
|
||||||
if (promptEl) autoGrowPrompt(promptEl);
|
if (promptEl) autoGrowPrompt(promptEl);
|
||||||
|
if (_sbPoseFilter) sbFilterPoses(_sbPoseFilter); // re-apply after rebuild
|
||||||
}
|
}
|
||||||
|
|
||||||
// Grow the custom-prompt textarea to fit its content (capped by max-height in CSS).
|
// Grow the custom-prompt textarea to fit its content (capped by max-height in CSS).
|
||||||
@@ -4638,33 +4669,36 @@
|
|||||||
// History autocomplete for the custom-prompt textarea. <datalist> can't bind to a
|
// History autocomplete for the custom-prompt textarea. <datalist> can't bind to a
|
||||||
// <textarea>, so this reuses the same `batchPromptHistory` store that feeds the
|
// <textarea>, so this reuses the same `batchPromptHistory` store that feeds the
|
||||||
// #promptHistory datalist and renders a small suggestion dropdown.
|
// #promptHistory datalist and renders a small suggestion dropdown.
|
||||||
function showPromptSuggest(el) {
|
// Prompt history dropdown — shared by the Generate and Scenery prompt textareas.
|
||||||
const box = document.getElementById('sbPromptSuggest');
|
// Pass the suggestion-box id so each textarea drives its own dropdown.
|
||||||
|
function showPromptSuggest(el, boxId = 'sbPromptSuggest') {
|
||||||
|
const box = document.getElementById(boxId);
|
||||||
if (!box) return;
|
if (!box) return;
|
||||||
const hist = JSON.parse(localStorage.getItem('batchPromptHistory') || '[]');
|
const hist = JSON.parse(localStorage.getItem('batchPromptHistory') || '[]');
|
||||||
const q = (el.value || '').trim().toLowerCase();
|
const q = (el.value || '').trim().toLowerCase();
|
||||||
const matches = (q ? hist.filter(p => p.toLowerCase().includes(q) && p.toLowerCase() !== q) : hist).slice(0, 10);
|
const matches = (q ? hist.filter(p => p.toLowerCase().includes(q) && p.toLowerCase() !== q) : hist).slice(0, 40);
|
||||||
if (!matches.length) { box.style.display = 'none'; return; }
|
if (!matches.length) { box.style.display = 'none'; return; }
|
||||||
box.style.top = (el.offsetTop + el.offsetHeight) + 'px';
|
box.style.top = (el.offsetTop + el.offsetHeight) + 'px';
|
||||||
|
const inputId = el.id;
|
||||||
box.innerHTML = matches.map(p =>
|
box.innerHTML = matches.map(p =>
|
||||||
`<div class="prompt-suggest-item" onmousedown="event.preventDefault();pickPromptSuggest(this)"
|
`<div class="prompt-suggest-item" onmousedown="event.preventDefault();pickPromptSuggest(this,'${inputId}','${boxId}')"
|
||||||
style="padding:6px 9px;font-size:11px;color:#ccc;cursor:pointer;border-bottom:1px solid #222;white-space:nowrap;overflow:hidden;text-overflow:ellipsis"
|
style="padding:6px 9px;font-size:11px;color:#ccc;cursor:pointer;border-bottom:1px solid #222;white-space:nowrap;overflow:hidden;text-overflow:ellipsis"
|
||||||
onmouseover="this.style.background='#1f2937'" onmouseout="this.style.background=''"
|
onmouseover="this.style.background='#1f2937'" onmouseout="this.style.background=''"
|
||||||
data-val="${escHtml(p)}" title="${escHtml(p)}">${escHtml(p)}</div>`).join('');
|
data-val="${escHtml(p)}" title="${escHtml(p)}">${escHtml(p)}</div>`).join('');
|
||||||
box.style.display = 'block';
|
box.style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
function hidePromptSuggest() {
|
function hidePromptSuggest(boxId = 'sbPromptSuggest') {
|
||||||
const box = document.getElementById('sbPromptSuggest');
|
const box = document.getElementById(boxId);
|
||||||
if (box) box.style.display = 'none';
|
if (box) box.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
function pickPromptSuggest(item) {
|
function pickPromptSuggest(item, inputId = 'sbGenPromptInput', boxId = 'sbPromptSuggest') {
|
||||||
const el = document.getElementById('sbGenPromptInput');
|
const el = document.getElementById(inputId);
|
||||||
if (!el) return;
|
if (!el) return;
|
||||||
el.value = item.dataset.val;
|
el.value = item.dataset.val;
|
||||||
hidePromptSuggest();
|
hidePromptSuggest(boxId);
|
||||||
updateSbGenBtn();
|
if (inputId === 'sbGenPromptInput') updateSbGenBtn();
|
||||||
autoGrowPrompt(el);
|
autoGrowPrompt(el);
|
||||||
el.focus();
|
el.focus();
|
||||||
}
|
}
|
||||||
@@ -4753,6 +4787,16 @@
|
|||||||
renderSidebarGenerate();
|
renderSidebarGenerate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Live client-side filter of the pose grid by name substring (no re-render).
|
||||||
|
function sbFilterPoses(q) {
|
||||||
|
_sbPoseFilter = q || '';
|
||||||
|
const needle = _sbPoseFilter.trim().toLowerCase();
|
||||||
|
document.querySelectorAll('#sbPosesGrid .sb-pose-btn').forEach(btn => {
|
||||||
|
const name = btn.dataset.poseName || '';
|
||||||
|
btn.style.display = (!needle || name.includes(needle)) ? '' : 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function toggleSbAngle(name) {
|
function toggleSbAngle(name) {
|
||||||
if (_sbSelectedAngles.has(name)) _sbSelectedAngles.delete(name);
|
if (_sbSelectedAngles.has(name)) _sbSelectedAngles.delete(name);
|
||||||
else _sbSelectedAngles.add(name);
|
else _sbSelectedAngles.add(name);
|
||||||
@@ -5342,40 +5386,59 @@
|
|||||||
let _sceneVideo = null, _sceneDuration = 0, _sceneFrameBytes = null, _sceneJobPollTimer = null;
|
let _sceneVideo = null, _sceneDuration = 0, _sceneFrameBytes = null, _sceneJobPollTimer = null;
|
||||||
|
|
||||||
let _sceneGridOpen = false; // whether the video picker grid is expanded
|
let _sceneGridOpen = false; // whether the video picker grid is expanded
|
||||||
let _sceneExtraRef = null; // image3 — optional extra reference filename (from filmstrip)
|
// 3 positional reference slots: 0=Background(image1), 1=Person(image2), 2=Extra(image3).
|
||||||
|
// Each entry is null | {kind:'bytes', data:<base64>} | {kind:'file', name:<filename>}.
|
||||||
|
// Backend contract: slot0→scene_bytes (bytes; a file here is fetched→bytes on submit),
|
||||||
|
// slot1→model_filename (must be a file), slot2→extra_filename (file, optional).
|
||||||
|
let _sceneRefs = [null, null, null];
|
||||||
|
let _scenePersonInit = false; // person auto-filled into slot1 once per group
|
||||||
|
let _sceneDrag = null; // active drag payload: {type:'slot',idx} | {type:'file',name}
|
||||||
|
|
||||||
function renderSidebarScenery() {
|
function renderSidebarScenery() {
|
||||||
const panel = document.getElementById('sbPanelScenery');
|
const panel = document.getElementById('sbPanelScenery');
|
||||||
if (!panel) return;
|
if (!panel) return;
|
||||||
const haveRef = _sceneVideo || _sceneFrameBytes;
|
// Preserve a typed prompt across the frequent re-renders (slot changes).
|
||||||
// Auto-open the grid when nothing is chosen yet.
|
const prevScenePrompt = document.getElementById('scenePromptInput')?.value || '';
|
||||||
|
// Auto-fill the Person slot (image2) once per group with the current image.
|
||||||
|
const personName = _fsModelFilename || lbNames[lbIdx] || '';
|
||||||
|
if (!_scenePersonInit && !_sceneRefs[1] && personName) {
|
||||||
|
_sceneRefs[1] = { kind: 'file', name: personName };
|
||||||
|
_scenePersonInit = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const bgRef = _sceneRefs[0];
|
||||||
|
const bgBytes = (bgRef && bgRef.kind === 'bytes') ? bgRef.data : null;
|
||||||
|
const haveRef = _sceneVideo || bgBytes;
|
||||||
|
// Auto-open the grid when no background source is chosen yet.
|
||||||
const gridOpen = _sceneGridOpen || !haveRef;
|
const gridOpen = _sceneGridOpen || !haveRef;
|
||||||
|
|
||||||
// Person (image2) = current studio image; extra (image3) = _sceneExtraRef
|
// --- Interactive 3-slot reference row (drag to reorder, ✕ to clear) ---
|
||||||
const personName = _fsModelFilename || lbNames[lbIdx] || '';
|
const SLOT_META = [
|
||||||
const personUrl = lbUrls[lbIdx] || '';
|
{ n: 1, label: 'Background' },
|
||||||
const personThumb = personName ? (isVideo(personName) ? posterFor(personUrl) : personUrl) : '';
|
{ n: 2, label: 'Person' },
|
||||||
const extraIdx = _sceneExtraRef ? lbNames.indexOf(_sceneExtraRef) : -1;
|
{ n: 3, label: 'Extra' },
|
||||||
const extraUrl = extraIdx >= 0 ? lbUrls[extraIdx] : '';
|
];
|
||||||
const extraThumb = _sceneExtraRef ? (isVideo(_sceneExtraRef) ? posterFor(extraUrl) : extraUrl) : '';
|
const slotHtml = SLOT_META.map((m, i) => {
|
||||||
|
const ref = _sceneRefs[i];
|
||||||
// --- 3-slot reference summary (what feeds the multi-ref compose) ---
|
const thumb = _sceneThumb(ref);
|
||||||
const slot = (cls, num, label, thumb) => `
|
const kind = ref ? (ref.kind === 'bytes' ? '📷' : '🖼') : '';
|
||||||
<div class="scene-slot ${thumb?'filled':'empty'}">
|
return `<div class="scene-slot ${ref ? 'filled' : 'empty'}" data-slot="${i}"
|
||||||
|
draggable="${ref ? 'true' : 'false'}"
|
||||||
|
ondragstart="sceneDragSlotStart(event,${i})"
|
||||||
|
ondragover="sceneDragOver(event)" ondragleave="sceneDragLeave(event)" ondrop="sceneDrop(event,${i})">
|
||||||
<div class="scene-slot-thumb">${thumb
|
<div class="scene-slot-thumb">${thumb
|
||||||
? `<img src="${thumb}" onerror="this.style.opacity=.3">`
|
? `<img src="${thumb}" onerror="this.style.opacity=.3">`
|
||||||
: `<span class="ph">${num}</span>`}</div>
|
: `<span class="ph">${m.n}</span>`}
|
||||||
<div class="scene-slot-cap"><b>Image ${num}</b>${label}</div>
|
${ref ? `<button class="scene-slot-x" title="Remove" onclick="sceneClearSlot(${i})">✕</button>` : ''}
|
||||||
|
${kind ? `<span class="scene-slot-kind">${kind}</span>` : ''}</div>
|
||||||
|
<div class="scene-slot-cap"><b>Image ${m.n}</b>${m.label}</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
let html = `<div class="sb-label">Compose: person + scene → one photo</div>
|
}).join('');
|
||||||
<div class="scene-slots">
|
let html = `<div class="sb-label">References <span style="color:#555;font-weight:400;font-size:9px">· Shift+Click filmstrip (fills →) · drag to reorder</span></div>
|
||||||
${slot('bg','1','Background', _sceneFrameBytes ? `data:image/png;base64,${_sceneFrameBytes}` : '')}
|
<div class="scene-slots">${slotHtml}</div>`;
|
||||||
${slot('person','2','Person', personThumb)}
|
|
||||||
${slot('extra','3','Extra (opt)', extraThumb)}
|
|
||||||
</div>`;
|
|
||||||
|
|
||||||
// --- IMAGE 1: Background scene (video scrub + capture, or upload) ---
|
// --- Image 1 source: background scene (video scrub + capture, or upload) ---
|
||||||
html += `<div class="sb-label">Image 1 · Background scene</div>`;
|
html += `<div class="sb-label">Image 1 source · background</div>`;
|
||||||
|
|
||||||
// --- Selected video: player + slider + capture (always at top) ---
|
// --- Selected video: player + slider + capture (always at top) ---
|
||||||
if (_sceneVideo) {
|
if (_sceneVideo) {
|
||||||
@@ -5420,56 +5483,47 @@
|
|||||||
html += `<input type="file" id="sceneUploadInput" accept="image/*" style="display:none" onchange="sceneHandleUpload(event)">
|
html += `<input type="file" id="sceneUploadInput" accept="image/*" style="display:none" onchange="sceneHandleUpload(event)">
|
||||||
<button class="sb-btn" onclick="document.getElementById('sceneUploadInput').click()" style="width:100%;margin-bottom:10px">⬆ Upload image as background</button>`;
|
<button class="sb-btn" onclick="document.getElementById('sceneUploadInput').click()" style="width:100%;margin-bottom:10px">⬆ Upload image as background</button>`;
|
||||||
|
|
||||||
// --- IMAGE 2: Person (auto — current studio image) ---
|
// --- Person / Extra: filled via filmstrip Shift+Click (see slots above) ---
|
||||||
|
const person = _sceneRefs[1];
|
||||||
html += `<div class="sb-sep"></div>
|
html += `<div class="sb-sep"></div>
|
||||||
<div class="sb-label">Image 2 · Person <span style="color:#555;font-weight:400;font-size:9px">· current image</span></div>`;
|
<div style="font-size:10px;color:#666;line-height:1.5;margin-bottom:8px">
|
||||||
if (personThumb) {
|
<b style="color:#999">Image 2 (Person)</b> & <b style="color:#999">Image 3 (Extra)</b>:
|
||||||
html += `<div style="display:flex;align-items:center;gap:8px;margin-bottom:10px">
|
Shift+Click a thumbnail in the bottom filmstrip to add it (fills the first open
|
||||||
<img src="${personThumb}" style="width:46px;height:46px;object-fit:cover;border-radius:5px;border:1px solid #2563eb"
|
slot right→left). Drag the slots above to rearrange, or ✕ to clear.
|
||||||
onerror="this.style.opacity=.3">
|
</div>`;
|
||||||
<span style="font-size:10px;color:#888;word-break:break-all">${escHtml(personName)}</span>
|
if (!person || person.kind !== 'file') {
|
||||||
</div>`;
|
html += `<div style="font-size:11px;color:#a55;padding:0 0 8px">Image 2 needs a gallery image (Shift+Click the filmstrip).</div>`;
|
||||||
} else {
|
|
||||||
html += `<div style="font-size:11px;color:#a55;padding:4px 0 10px">No person image — open one from the gallery first.</div>`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- IMAGE 3: Extra reference (optional, pick from this group) ---
|
|
||||||
html += `<div class="sb-sep"></div>
|
|
||||||
<div class="sb-label">Image 3 · Extra reference <span style="color:#555;font-weight:400;font-size:9px">· optional · click to toggle</span></div>`;
|
|
||||||
const pickNames = lbNames.filter(n => n && n !== personName);
|
|
||||||
if (pickNames.length) {
|
|
||||||
html += '<div class="scene-pick-row">';
|
|
||||||
pickNames.forEach(n => {
|
|
||||||
const idx = lbNames.indexOf(n);
|
|
||||||
const u = lbUrls[idx] || '';
|
|
||||||
const th = isVideo(n) ? posterFor(u) : u;
|
|
||||||
const sel = _sceneExtraRef === n;
|
|
||||||
const nSafe = n.replace(/'/g, "\\'");
|
|
||||||
html += `<div class="scene-pick ${sel?'sel':''}" title="${escHtml(n)}"
|
|
||||||
onclick="sceneToggleExtraRef('${nSafe}')">
|
|
||||||
<img src="${th}" loading="lazy" onerror="this.style.opacity=.3">
|
|
||||||
${sel?'<div class="badge3">3</div>':''}</div>`;
|
|
||||||
});
|
|
||||||
html += '</div>';
|
|
||||||
} else {
|
|
||||||
html += `<div style="font-size:11px;color:#555;padding:4px 0">No other images in this group.</div>`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Prompt + generate ---
|
// --- Prompt + generate ---
|
||||||
|
const canGen = !!bgRef && !!person && person.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)</div>
|
<div class="sb-label">Prompt (optional) <span style="color:#555;font-weight:400;font-size:9px">· history while typing</span></div>
|
||||||
<input type="text" class="sb-input" id="scenePromptInput"
|
<div class="sb-prompt-wrap" style="position:relative;margin-bottom:10px">
|
||||||
placeholder="Auto: place person in scene…" style="margin-bottom:10px">
|
<textarea class="sb-input" id="scenePromptInput" rows="2" autocomplete="off"
|
||||||
|
placeholder="Auto: place person in scene… (type to search history)"
|
||||||
|
oninput="autoGrowPrompt(this);showPromptSuggest(this,'scenePromptSuggest')"
|
||||||
|
onfocus="autoGrowPrompt(this);showPromptSuggest(this,'scenePromptSuggest')"
|
||||||
|
onblur="setTimeout(()=>hidePromptSuggest('scenePromptSuggest'),150)"
|
||||||
|
onkeydown="if(event.key==='Escape')hidePromptSuggest('scenePromptSuggest')"
|
||||||
|
style="resize:none;max-height:300px;overflow-y:auto;line-height:1.4;font-family:inherit"></textarea>
|
||||||
|
<div id="scenePromptSuggest" style="display:none;position:absolute;left:0;right:0;z-index:50;max-height:240px;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;gap:8px">
|
<div style="display:flex;align-items:center;gap:8px">
|
||||||
<span id="sceneJobStatus" style="flex:1;font-size:11px;color:#888;display:none">
|
<span id="sceneJobStatus" style="flex:1;font-size:11px;color:#888;display:none">
|
||||||
<span class="sb-spinner"></span><span id="sceneJobText">Generating…</span>
|
<span class="sb-spinner"></span><span id="sceneJobText">Generating…</span>
|
||||||
</span>
|
</span>
|
||||||
<button class="sb-btn primary" id="sceneGenBtn" onclick="submitGenerateScenery()"
|
<button class="sb-btn primary" id="sceneGenBtn" onclick="submitGenerateScenery()"
|
||||||
${(haveRef && personName)?'':'disabled'}>Create Scenery</button>
|
${canGen?'':'disabled'}>Create Scenery</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
|
||||||
panel.innerHTML = html;
|
panel.innerHTML = html;
|
||||||
|
|
||||||
|
// Restore the preserved prompt text and size the textarea.
|
||||||
|
const spEl = document.getElementById('scenePromptInput');
|
||||||
|
if (spEl) { spEl.value = prevScenePrompt; autoGrowPrompt(spEl); }
|
||||||
|
|
||||||
if (_sceneVideo) {
|
if (_sceneVideo) {
|
||||||
const vid = document.getElementById('sceneVideoEl');
|
const vid = document.getElementById('sceneVideoEl');
|
||||||
vid.src = `${API}/wireframe/${encodeURIComponent(_sceneVideo)}`;
|
vid.src = `${API}/wireframe/${encodeURIComponent(_sceneVideo)}`;
|
||||||
@@ -5503,20 +5557,106 @@
|
|||||||
renderSidebarScenery();
|
renderSidebarScenery();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Toggle image3 (extra reference) selection from the in-group picker.
|
// ---- scenery reference-slot helpers ----
|
||||||
function sceneToggleExtraRef(name) {
|
|
||||||
_sceneExtraRef = (_sceneExtraRef === name) ? null : name;
|
// Thumbnail (URL or data-URL) for a slot ref.
|
||||||
renderSidebarScenery();
|
function _sceneThumb(ref) {
|
||||||
|
if (!ref) return '';
|
||||||
|
if (ref.kind === 'bytes') return 'data:image/png;base64,' + ref.data;
|
||||||
|
const idx = lbNames.indexOf(ref.name);
|
||||||
|
const u = idx >= 0 ? lbUrls[idx] : (IMAGE_FOLDER + ref.name);
|
||||||
|
return isVideo(ref.name) ? posterFor(u) : u;
|
||||||
|
}
|
||||||
|
function _sceneUrlForName(name) {
|
||||||
|
const idx = lbNames.indexOf(name);
|
||||||
|
return idx >= 0 ? lbUrls[idx] : (IMAGE_FOLDER + name);
|
||||||
|
}
|
||||||
|
async function _fetchAsBase64(url) {
|
||||||
|
const r = await fetch(url);
|
||||||
|
const blob = await r.blob();
|
||||||
|
return await new Promise((res, rej) => {
|
||||||
|
const fr = new FileReader();
|
||||||
|
fr.onload = () => res(String(fr.result).split(',')[1]);
|
||||||
|
fr.onerror = rej;
|
||||||
|
fr.readAsDataURL(blob);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh both the scenery panel and the filmstrip badges.
|
||||||
|
function _sceneRefresh() { renderSidebarScenery(); updateStudio(); }
|
||||||
|
|
||||||
|
// Assign a filmstrip file to the first empty slot scanning right→left ([2,1,0]).
|
||||||
|
// Shift+Click an already-assigned file removes it (toggle).
|
||||||
|
function sceneAssignRef(name) {
|
||||||
|
const at = _sceneRefs.findIndex(r => r && r.kind === 'file' && r.name === name);
|
||||||
|
if (at >= 0) { _sceneRefs[at] = null; _sceneRefresh(); return; }
|
||||||
|
for (const i of [2, 1, 0]) {
|
||||||
|
if (!_sceneRefs[i]) {
|
||||||
|
_sceneRefs[i] = { kind: 'file', name };
|
||||||
|
if (i === 0) _sceneFrameBytes = null; // a file now backs slot 0
|
||||||
|
_sceneRefresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showToast('All 3 reference slots are full — ✕ one first', 'info');
|
||||||
|
}
|
||||||
|
|
||||||
|
function sceneClearSlot(i) {
|
||||||
|
const ref = _sceneRefs[i];
|
||||||
|
_sceneRefs[i] = null;
|
||||||
|
if (i === 0 && ref && ref.kind === 'bytes') _sceneFrameBytes = null;
|
||||||
|
if (i === 1) _scenePersonInit = true; // don't auto-refill after explicit clear
|
||||||
|
_sceneRefresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- drag & drop between slots / from filmstrip ----
|
||||||
|
function sceneDragSlotStart(ev, i) {
|
||||||
|
if (!_sceneRefs[i]) { ev.preventDefault(); return; }
|
||||||
|
_sceneDrag = { type: 'slot', idx: i };
|
||||||
|
ev.dataTransfer.effectAllowed = 'move';
|
||||||
|
}
|
||||||
|
function sceneFilmDragStart(ev, i) {
|
||||||
|
_sceneDrag = { type: 'file', name: lbNames[i] };
|
||||||
|
ev.dataTransfer.effectAllowed = 'copy';
|
||||||
|
}
|
||||||
|
function sceneDragOver(ev) { ev.preventDefault(); ev.currentTarget.classList.add('drop-hover'); }
|
||||||
|
function sceneDragLeave(ev) { ev.currentTarget.classList.remove('drop-hover'); }
|
||||||
|
function sceneDrop(ev, dst) {
|
||||||
|
ev.preventDefault();
|
||||||
|
ev.currentTarget.classList.remove('drop-hover');
|
||||||
|
const drag = _sceneDrag; _sceneDrag = null;
|
||||||
|
if (!drag) return;
|
||||||
|
if (drag.type === 'file') {
|
||||||
|
// Dropping a filmstrip image onto a slot — remove any duplicate first.
|
||||||
|
const dup = _sceneRefs.findIndex(r => r && r.kind === 'file' && r.name === drag.name);
|
||||||
|
if (dup >= 0 && dup !== dst) _sceneRefs[dup] = null;
|
||||||
|
if (dst === 0 && _sceneRefs[0] && _sceneRefs[0].kind === 'bytes') _sceneFrameBytes = null;
|
||||||
|
_sceneRefs[dst] = { kind: 'file', name: drag.name };
|
||||||
|
} else {
|
||||||
|
const src = drag.idx;
|
||||||
|
if (src === dst) return;
|
||||||
|
const a = _sceneRefs[src], b = _sceneRefs[dst];
|
||||||
|
// Captured/uploaded bytes can only live in slot 0 (the background).
|
||||||
|
if ((a && a.kind === 'bytes' && dst !== 0) || (b && b.kind === 'bytes' && src !== 0)) {
|
||||||
|
showToast('The captured background can only sit in the Image 1 slot', 'info');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_sceneRefs[src] = b; _sceneRefs[dst] = a;
|
||||||
|
_sceneFrameBytes = (_sceneRefs[0] && _sceneRefs[0].kind === 'bytes') ? _sceneRefs[0].data : null;
|
||||||
|
}
|
||||||
|
_sceneRefresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sceneSelectVideo(v) {
|
async function sceneSelectVideo(v) {
|
||||||
_sceneVideo = v; _sceneFrameBytes = null;
|
_sceneVideo = v; _sceneFrameBytes = null;
|
||||||
|
if (_sceneRefs[0] && _sceneRefs[0].kind === 'bytes') _sceneRefs[0] = null;
|
||||||
_sceneGridOpen = false; // collapse the picker so the player is front-and-centre
|
_sceneGridOpen = false; // collapse the picker so the player is front-and-centre
|
||||||
renderSidebarScenery();
|
renderSidebarScenery();
|
||||||
}
|
}
|
||||||
|
|
||||||
function sceneReleaseFrame() {
|
function sceneReleaseFrame() {
|
||||||
_sceneFrameBytes = null;
|
_sceneFrameBytes = null;
|
||||||
|
if (_sceneRefs[0] && _sceneRefs[0].kind === 'bytes') _sceneRefs[0] = null;
|
||||||
renderSidebarScenery(); // re-render restores the live video + slider + capture button
|
renderSidebarScenery(); // re-render restores the live video + slider + capture button
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5538,7 +5678,8 @@
|
|||||||
} else {
|
} else {
|
||||||
const d = await r.json();
|
const d = await r.json();
|
||||||
_sceneFrameBytes = d.frame_b64;
|
_sceneFrameBytes = d.frame_b64;
|
||||||
renderSidebarScenery(); // show the frozen frame + "pick a different frame"
|
_sceneRefs[0] = { kind: 'bytes', data: d.frame_b64 }; // background → slot 0
|
||||||
|
_sceneRefresh(); // show the frozen frame + update slot badge
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showToast('Frame extract error: ' + e, 'error');
|
showToast('Frame extract error: ' + e, 'error');
|
||||||
@@ -5552,6 +5693,7 @@
|
|||||||
const reader = new FileReader();
|
const reader = new FileReader();
|
||||||
reader.onload = e => {
|
reader.onload = e => {
|
||||||
_sceneFrameBytes = e.target.result.split(',')[1];
|
_sceneFrameBytes = e.target.result.split(',')[1];
|
||||||
|
_sceneRefs[0] = { kind: 'bytes', data: _sceneFrameBytes }; // background → slot 0
|
||||||
_sceneVideo = null;
|
_sceneVideo = null;
|
||||||
renderSidebarScenery();
|
renderSidebarScenery();
|
||||||
};
|
};
|
||||||
@@ -5559,9 +5701,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function submitGenerateScenery() {
|
async function submitGenerateScenery() {
|
||||||
if (!_fsModelFilename) return;
|
// Map positional slots → backend contract.
|
||||||
if (!_sceneVideo && !_sceneFrameBytes) { showToast('Pick a scene reference first', 'error'); return; }
|
const bg = _sceneRefs[0], person = _sceneRefs[1], extra = _sceneRefs[2];
|
||||||
const sceneVid = document.getElementById('sceneVideoEl');
|
if (!bg) { showToast('Set a background (Image 1) — capture, upload, or Shift+Click', 'error'); return; }
|
||||||
|
if (!person || person.kind !== 'file') { showToast('Set a person image (Image 2) from the filmstrip', 'error'); return; }
|
||||||
|
if (extra && extra.kind !== 'file') { showToast('Image 3 must be a gallery image', 'error'); return; }
|
||||||
const prompt = (document.getElementById('scenePromptInput')?.value || '').trim() || null;
|
const prompt = (document.getElementById('scenePromptInput')?.value || '').trim() || null;
|
||||||
const btn = document.getElementById('sceneGenBtn');
|
const btn = document.getElementById('sceneGenBtn');
|
||||||
const statusEl = document.getElementById('sceneJobStatus');
|
const statusEl = document.getElementById('sceneJobStatus');
|
||||||
@@ -5569,12 +5713,21 @@
|
|||||||
if (btn) btn.disabled = true;
|
if (btn) btn.disabled = true;
|
||||||
if (statusEl) statusEl.style.display = 'inline-flex';
|
if (statusEl) statusEl.style.display = 'inline-flex';
|
||||||
if (textEl) textEl.textContent = 'Submitting…';
|
if (textEl) textEl.textContent = 'Submitting…';
|
||||||
|
if (prompt) savePromptHistory(prompt);
|
||||||
|
// slot0 → scene_bytes: bytes directly, or fetch a file-as-background to bytes.
|
||||||
|
let sceneBytes;
|
||||||
|
try {
|
||||||
|
sceneBytes = (bg.kind === 'bytes') ? bg.data : await _fetchAsBase64(_sceneUrlForName(bg.name));
|
||||||
|
} catch (e) {
|
||||||
|
showToast('Could not read background image: ' + e, 'error');
|
||||||
|
if (btn) btn.disabled = false; if (statusEl) statusEl.style.display = 'none'; return;
|
||||||
|
}
|
||||||
const payload = {
|
const payload = {
|
||||||
model_filename: _fsModelFilename, // image2 (Picture 2) — person
|
model_filename: person.name, // image2 (Picture 2) — person
|
||||||
scene_bytes: _sceneFrameBytes || null, // image1 (Picture 1) — background
|
scene_bytes: sceneBytes, // image1 (Picture 1) — background
|
||||||
scene_video: _sceneVideo || null,
|
scene_video: null,
|
||||||
scene_time: sceneVid ? sceneVid.currentTime : 0,
|
scene_time: 0,
|
||||||
extra_filename: _sceneExtraRef || null, // image3 (Picture 3) — optional extra ref
|
extra_filename: (extra && extra.kind === 'file') ? extra.name : null, // image3 (Picture 3)
|
||||||
prompt,
|
prompt,
|
||||||
seed: -1,
|
seed: -1,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user