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:
@@ -1834,7 +1834,10 @@
|
||||
</div>
|
||||
<div id="lbFaceBook" style="display:none;margin-bottom:8px">
|
||||
<div class="sb-label" style="margin-bottom:4px">Face reference</div>
|
||||
<img id="lbFaceThumb" style="width:72px;height:72px;object-fit:cover;border-radius:6px;border:1px solid #333;cursor:pointer" onclick="window.open(this.src,'_blank')" title="Face crop — click to view full">
|
||||
<div style="display:flex;gap:8px;align-items:flex-start">
|
||||
<img id="lbFaceThumb" style="width:72px;height:72px;object-fit:cover;border-radius:6px;border:1px solid #333;cursor:pointer;flex-shrink:0" onclick="window.open(this.src,'_blank')" title="Face crop — click to view full">
|
||||
<button class="sb-btn" onclick="lbFindSimilarFaces()" title="Find groups with matching faces">Similar faces</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sb-sep"></div>
|
||||
<div class="sb-label">Order & visibility</div>
|
||||
@@ -2968,6 +2971,86 @@
|
||||
if (btn) btn.disabled = false;
|
||||
}
|
||||
|
||||
async function lbFindSimilarFaces() {
|
||||
if (!lbCurrentGid) return;
|
||||
try {
|
||||
const r = await fetch(`${API}/faces/similar`, {
|
||||
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ group_id: lbCurrentGid, limit: 12 }),
|
||||
});
|
||||
if (r.status === 404) { _renderFaceResults(null); return; }
|
||||
if (!r.ok) { showToast('Similar faces failed: ' + await r.text(), 'error'); return; }
|
||||
const d = await r.json();
|
||||
_renderFaceResults(d.similar || []);
|
||||
} catch (e) { showToast('Similar faces failed: ' + e, 'error'); }
|
||||
}
|
||||
|
||||
async function lbBuildFaceIndex() {
|
||||
try {
|
||||
const r = await fetch(`${API}/faces/index`, { method: 'POST' });
|
||||
if (!r.ok) { showToast('Face index failed: ' + await r.text(), 'error'); return; }
|
||||
showToast('Building face index…', 'info');
|
||||
_pollFaceIndex();
|
||||
} catch (e) { showToast('Face index failed: ' + e, 'error'); }
|
||||
}
|
||||
|
||||
async function _pollFaceIndex() {
|
||||
try {
|
||||
const r = await fetch(`${API}/faces/index/status`);
|
||||
if (r.ok) {
|
||||
const s = await r.json();
|
||||
if (s.running) {
|
||||
showToast(`Face index: ${s.done}/${s.total}…`, 'info', 2500);
|
||||
setTimeout(_pollFaceIndex, 2000);
|
||||
} else {
|
||||
showToast(`Face index ready (${s.indexed} embeddings)`, 'success');
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
function _renderFaceResults(items) {
|
||||
document.getElementById('faceResults')?.remove();
|
||||
const viewer = document.getElementById('studioViewer');
|
||||
if (!viewer) return;
|
||||
const panel = document.createElement('div');
|
||||
panel.id = 'faceResults';
|
||||
panel.style.cssText = 'position:absolute;left:0;right:0;bottom:0;z-index:103;background:rgba(0,0,0,0.85);'
|
||||
+ 'padding:8px;display:flex;gap:8px;overflow-x:auto;align-items:center;';
|
||||
if (!items || !items.length) {
|
||||
const msg = items === null
|
||||
? 'No face embedding for this group — build the index first'
|
||||
: 'No similar faces found';
|
||||
panel.innerHTML = `<span style="color:#aaa;font-size:12px;flex:1">${msg}</span>`
|
||||
+ '<button class="sb-btn" onclick="lbBuildFaceIndex();document.getElementById(\'faceResults\')?.remove()">Build index</button>'
|
||||
+ '<button class="sb-btn" onclick="document.getElementById(\'faceResults\')?.remove()">Close</button>';
|
||||
} else {
|
||||
panel.innerHTML = '<span style="color:#aaa;font-size:11px;flex-shrink:0">Similar faces:</span>'
|
||||
+ items.map(it => {
|
||||
const u = IMAGE_FOLDER + it.filename + '?t=' + Date.now();
|
||||
const gid = (it.group_id || '').replace(/'/g, "\\'");
|
||||
const fn = it.filename.replace(/'/g, "\\'");
|
||||
return `<div title="dist ${it.distance}" style="flex-shrink:0;cursor:pointer;text-align:center"
|
||||
onclick="openFaceResult('${gid}','${fn}')">
|
||||
<img src="${u}" loading="lazy" style="width:64px;height:64px;object-fit:cover;border-radius:5px;border:1px solid #444" onerror="this.style.opacity='0.3'">
|
||||
<div style="font-size:9px;color:#777">${it.distance}</div></div>`;
|
||||
}).join('')
|
||||
+ '<button class="sb-btn" style="flex-shrink:0" onclick="document.getElementById(\'faceResults\')?.remove()">Close</button>';
|
||||
}
|
||||
viewer.appendChild(panel);
|
||||
}
|
||||
|
||||
function openFaceResult(gid, fname) {
|
||||
document.getElementById('faceResults')?.remove();
|
||||
if (gid && groupData.has(gid)) {
|
||||
openStudio(gid, 0);
|
||||
} else {
|
||||
const i = lbNames.indexOf(fname);
|
||||
if (i >= 0) { lbIdx = i; updateStudio(); }
|
||||
else showToast('Group not found in gallery: ' + fname, 'info', 5000);
|
||||
}
|
||||
}
|
||||
|
||||
function _renderPoseResults(items) {
|
||||
document.getElementById('poseResults')?.remove();
|
||||
const viewer = document.getElementById('studioViewer');
|
||||
@@ -5236,19 +5319,15 @@
|
||||
html += '</div>';
|
||||
}
|
||||
html += `<div class="scene-frame-preview" id="sceneFramePreview" style="${_sceneVideo||_sceneFrameBytes?'':'display:none'}">
|
||||
<video id="sceneVideoEl" muted preload="auto" playsinline autoplay loop
|
||||
<video id="sceneVideoEl" muted preload="auto" playsinline loop controls
|
||||
style="${_sceneVideo&&!_sceneFrameBytes?'':'display:none'};width:100%;height:100%;object-fit:contain"></video>
|
||||
<img id="sceneFrameImg" style="${_sceneFrameBytes?'':'display:none'};width:100%;height:100%;object-fit:contain"
|
||||
${_sceneFrameBytes?`src="data:image/png;base64,${_sceneFrameBytes}"`:''}/>
|
||||
<div id="sceneFrameLoading" style="display:none;position:absolute;inset:0;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;font-size:11px;color:#aaa">Loading…</div>
|
||||
</div>
|
||||
<input type="range" class="scene-scrubber" id="sceneScrubber"
|
||||
min="0" max="${_sceneDuration||100}" value="0" step="0.1"
|
||||
oninput="sceneSeekVideo(this.value)"
|
||||
style="${_sceneVideo?'':'display:none'}">
|
||||
<div id="sceneTimeLabel" style="font-size:10px;color:#555;margin-bottom:4px;${_sceneVideo?'':'display:none'}">0:00</div>
|
||||
<button class="sb-btn" id="sceneExtractBtn" onclick="sceneExtractFrame()"
|
||||
style="${_sceneVideo?'':'display:none'};margin-bottom:8px">Extract frame as reference</button>
|
||||
style="${_sceneVideo&&!_sceneFrameBytes?'':'display:none'};margin-bottom:8px">Extract frame as reference</button>
|
||||
<button class="sb-btn" id="sceneReleaseBtn" onclick="sceneReleaseFrame()"
|
||||
style="${_sceneFrameBytes?'':'display:none'};margin-bottom:8px">✖ Change frame</button>
|
||||
<div class="sb-sep"></div>
|
||||
<div class="sb-label">Or upload image</div>
|
||||
<input type="file" id="sceneUploadInput" accept="image/*" style="display:none" onchange="sceneHandleUpload(event)">
|
||||
@@ -5268,10 +5347,9 @@
|
||||
if (_sceneVideo) {
|
||||
const vid = document.getElementById('sceneVideoEl');
|
||||
vid.src = `${API}/wireframe/${encodeURIComponent(_sceneVideo)}`;
|
||||
if (_sceneDuration) {
|
||||
const scrubber = document.getElementById('sceneScrubber');
|
||||
if (scrubber) { scrubber.max = _sceneDuration; scrubber.step = Math.max(0.05, _sceneDuration / 500); }
|
||||
}
|
||||
vid.addEventListener('seeking', () => {
|
||||
if (_sceneFrameBytes) sceneReleaseFrame();
|
||||
});
|
||||
}
|
||||
// Cards show static poster frames; videos mount only on hover (_tplPlay).
|
||||
}
|
||||
@@ -5279,58 +5357,20 @@
|
||||
async function sceneSelectVideo(v) {
|
||||
_sceneVideo = v; _sceneFrameBytes = null;
|
||||
renderSidebarScenery();
|
||||
try {
|
||||
const r = await fetch(`${API}/wireframe/duration/${encodeURIComponent(v)}`);
|
||||
if (r.ok) {
|
||||
_sceneDuration = (await r.json()).duration || 0;
|
||||
const scrubber = document.getElementById('sceneScrubber');
|
||||
if (scrubber) { scrubber.max = _sceneDuration; scrubber.step = Math.max(0.05, _sceneDuration / 500); }
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
let _sceneScrubTimer = null;
|
||||
|
||||
function sceneSeekVideo(val) {
|
||||
const t = parseFloat(val);
|
||||
const lbl = document.getElementById('sceneTimeLabel');
|
||||
if (lbl) lbl.textContent = formatSecs(t);
|
||||
// If a captured frame was locked, release it so scrubbing works again
|
||||
if (_sceneFrameBytes) {
|
||||
_sceneFrameBytes = null;
|
||||
const btn = document.getElementById('sceneExtractBtn');
|
||||
if (btn) btn.textContent = 'Extract frame as reference';
|
||||
const genBtn = document.getElementById('sceneGenBtn');
|
||||
if (genBtn) genBtn.disabled = !_sceneVideo;
|
||||
}
|
||||
// Show video live while the user is dragging
|
||||
function sceneReleaseFrame() {
|
||||
_sceneFrameBytes = null;
|
||||
const vid = document.getElementById('sceneVideoEl');
|
||||
const img = document.getElementById('sceneFrameImg');
|
||||
if (vid) { vid.style.display = ''; vid.currentTime = t; }
|
||||
const extractBtn = document.getElementById('sceneExtractBtn');
|
||||
const releaseBtn = document.getElementById('sceneReleaseBtn');
|
||||
const genBtn = document.getElementById('sceneGenBtn');
|
||||
if (vid) vid.style.display = '';
|
||||
if (img) img.style.display = 'none';
|
||||
// After settling, fetch exact server-rendered frame and freeze on it
|
||||
clearTimeout(_sceneScrubTimer);
|
||||
_sceneScrubTimer = setTimeout(() => _sceneShowFrame(t), 300);
|
||||
}
|
||||
|
||||
async function _sceneShowFrame(t) {
|
||||
if (!_sceneVideo) return;
|
||||
try {
|
||||
const r = await fetch(`${API}/wireframe/frame`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ video_name: _sceneVideo, time: t }),
|
||||
});
|
||||
if (!r.ok) return;
|
||||
const d = await r.json();
|
||||
const vid = document.getElementById('sceneVideoEl');
|
||||
const img = document.getElementById('sceneFrameImg');
|
||||
if (!img) return;
|
||||
if (vid) vid.style.display = 'none';
|
||||
img.src = 'data:image/png;base64,' + d.frame_b64;
|
||||
img.style.display = '';
|
||||
img.dataset.pendingFrame = d.frame_b64; // ready to capture without re-fetch
|
||||
} catch (_) { /* video stays visible on error */ }
|
||||
if (extractBtn) extractBtn.style.display = '';
|
||||
if (releaseBtn) releaseBtn.style.display = 'none';
|
||||
if (genBtn) genBtn.disabled = !_sceneVideo;
|
||||
}
|
||||
|
||||
async function sceneExtractFrame() {
|
||||
|
||||
Reference in New Issue
Block a user