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:
@@ -2922,8 +2922,13 @@
|
||||
bar.style.cssText = 'position:absolute;top:8px;left:8px;display:flex;gap:6px;z-index:102;';
|
||||
bar.innerHTML =
|
||||
'<button class="sb-btn" onclick="lbPoseReset()" title="Re-detect the original pose">Reset</button>'
|
||||
+ '<button class="sb-btn primary" onclick="lbFindSimilarPose()" title="Find library images in this pose">Similar pose</button>';
|
||||
+ '<button class="sb-btn primary" onclick="lbFindSimilarPose()" title="Find library images in this pose">Similar pose</button>'
|
||||
+ '<div style="position:relative;display:inline-block">'
|
||||
+ ' <button class="sb-btn" id="gestureMenuBtn" onclick="toggleGestureMenu(event)" title="Apply a gesture preset to the skeleton">Gesture ▾</button>'
|
||||
+ ' <div id="gestureMenuPanel" style="display:none;position:absolute;top:100%;left:0;z-index:200;background:#18181b;border:1px solid #2a2a2a;border-radius:6px;padding:4px;box-shadow:0 6px 20px rgba(0,0,0,.7);min-width:150px;margin-top:4px"></div>'
|
||||
+ '</div>';
|
||||
viewer.appendChild(bar);
|
||||
_buildGestureMenu();
|
||||
|
||||
canvas.addEventListener('mousedown', _posePointerDown);
|
||||
canvas.addEventListener('mousemove', _posePointerMove);
|
||||
@@ -2933,6 +2938,70 @@
|
||||
if (btn) btn.classList.add('primary');
|
||||
}
|
||||
|
||||
// --- Gesture presets: normalized [x,y,conf] per COCO-17 keypoint ---
|
||||
// Defined in [0,1] image space for a portrait-oriented standing figure.
|
||||
// Applied to the actual image by scaling to image pixel dimensions.
|
||||
const GESTURE_PRESETS = [
|
||||
{ name: 'T-pose', desc: 'Arms fully extended sideways',
|
||||
kpts: [[.50,.07,1],[.47,.065,1],[.53,.065,1],[.43,.08,1],[.57,.08,1],
|
||||
[.25,.22,1],[.75,.22,1],[.10,.22,1],[.90,.22,1],[-.02,.22,1],[1.02,.22,1],
|
||||
[.40,.52,1],[.60,.52,1],[.40,.72,1],[.60,.72,1],[.40,.93,1],[.60,.93,1]] },
|
||||
{ name: 'Wave right', desc: 'Right arm raised and waving',
|
||||
kpts: [[.50,.07,1],[.47,.065,1],[.53,.065,1],[.43,.08,1],[.57,.08,1],
|
||||
[.35,.22,1],[.65,.22,1],[.20,.38,1],[.78,.10,1],[.22,.52,1],[.70,.04,1],
|
||||
[.40,.52,1],[.60,.52,1],[.40,.72,1],[.60,.72,1],[.40,.93,1],[.60,.93,1]] },
|
||||
{ name: 'Arms raised', desc: 'Both arms raised above head',
|
||||
kpts: [[.50,.07,1],[.47,.065,1],[.53,.065,1],[.43,.08,1],[.57,.08,1],
|
||||
[.35,.22,1],[.65,.22,1],[.22,.08,1],[.78,.08,1],[.18,.02,1],[.82,.02,1],
|
||||
[.40,.52,1],[.60,.52,1],[.40,.72,1],[.60,.72,1],[.40,.93,1],[.60,.93,1]] },
|
||||
{ name: 'Hands on hips', desc: 'Hands resting on hips',
|
||||
kpts: [[.50,.07,1],[.47,.065,1],[.53,.065,1],[.43,.08,1],[.57,.08,1],
|
||||
[.35,.22,1],[.65,.22,1],[.28,.40,1],[.72,.40,1],[.35,.52,1],[.65,.52,1],
|
||||
[.38,.52,1],[.62,.52,1],[.38,.72,1],[.62,.72,1],[.38,.93,1],[.62,.93,1]] },
|
||||
{ name: 'Turn left 45°', desc: 'Body turned ~45° to the left',
|
||||
kpts: [[.45,.07,1],[.44,.065,1],[.50,.065,1],[.41,.08,1],[.52,.08,1],
|
||||
[.30,.22,1],[.58,.22,1],[.18,.38,1],[.65,.30,1],[.12,.52,1],[.70,.22,1],
|
||||
[.36,.52,1],[.58,.52,1],[.36,.72,1],[.58,.72,1],[.36,.93,1],[.58,.93,1]] },
|
||||
{ name: 'Turn right 45°', desc: 'Body turned ~45° to the right',
|
||||
kpts: [[.55,.07,1],[.50,.065,1],[.56,.065,1],[.48,.08,1],[.59,.08,1],
|
||||
[.42,.22,1],[.70,.22,1],[.35,.30,1],[.82,.38,1],[.30,.22,1],[.88,.52,1],
|
||||
[.42,.52,1],[.64,.52,1],[.42,.72,1],[.64,.72,1],[.42,.93,1],[.64,.93,1]] },
|
||||
];
|
||||
|
||||
function _buildGestureMenu() {
|
||||
const panel = document.getElementById('gestureMenuPanel');
|
||||
if (!panel) return;
|
||||
panel.innerHTML = GESTURE_PRESETS.map((g, i) =>
|
||||
`<div style="padding:5px 10px;cursor:pointer;font-size:11px;border-radius:4px;white-space:nowrap"
|
||||
title="${escHtml(g.desc)}"
|
||||
onmouseenter="this.style.background='#2a2a2a'" onmouseleave="this.style.background=''"
|
||||
onclick="applyGesturePreset(${i})">${escHtml(g.name)}</div>`
|
||||
).join('');
|
||||
}
|
||||
|
||||
function toggleGestureMenu(e) {
|
||||
e.stopPropagation();
|
||||
const p = document.getElementById('gestureMenuPanel');
|
||||
if (!p) return;
|
||||
const open = p.style.display !== 'none';
|
||||
p.style.display = open ? 'none' : 'block';
|
||||
if (!open) {
|
||||
const close = (ev) => { if (!p.contains(ev.target)) { p.style.display='none'; document.removeEventListener('click',close); } };
|
||||
setTimeout(() => document.addEventListener('click', close), 0);
|
||||
}
|
||||
}
|
||||
|
||||
function applyGesturePreset(idx) {
|
||||
const st = window._poseState;
|
||||
if (!st || !st.people.length) { showToast('No pose overlay active — click Pose first', 'info'); return; }
|
||||
const g = GESTURE_PRESETS[idx];
|
||||
const W = st.width, H = st.height;
|
||||
st.people[0] = g.kpts.map(([nx, ny, c]) => [nx * W, ny * H, c]);
|
||||
_drawPose();
|
||||
document.getElementById('gestureMenuPanel').style.display = 'none';
|
||||
showToast(`Gesture: ${g.name}`, 'success');
|
||||
}
|
||||
|
||||
function _drawPose() {
|
||||
const st = window._poseState; if (!st) return;
|
||||
const { ctx, canvas, scale, offX, offY } = st;
|
||||
@@ -4506,7 +4575,12 @@
|
||||
// Camera angles section — absolute positions
|
||||
const absAngles = CAMERA_ANGLES.filter(a => !a.relative);
|
||||
const relAngles = CAMERA_ANGLES.filter(a => a.relative);
|
||||
let html = '<div class="sb-label">Camera — absolute</div><div class="sb-camera-grid" id="sbCameraGrid">';
|
||||
const ORBIT_NAMES = ['Front', '¾ Left', '¾ Right', 'Side L', 'Side R', 'Back'];
|
||||
const allOrbitSel = ORBIT_NAMES.every(n => _sbSelectedAngles.has(n));
|
||||
let html = `<div style="display:flex;align-items:center;gap:6px;margin-bottom:3px">
|
||||
<span class="sb-label" style="margin:0;flex:1">Camera — absolute</span>
|
||||
<button class="sb-btn${allOrbitSel?' primary':''}" style="padding:2px 8px;font-size:10px" onclick="sbToggleOrbitAll()" title="Select all 6 orbit angles (front · ¾ · side · back)">${allOrbitSel?'✓ Orbit all':'Orbit all'}</button>
|
||||
</div><div class="sb-camera-grid" id="sbCameraGrid">`;
|
||||
html += absAngles.map(a => {
|
||||
const sel = _sbSelectedAngles.has(a.name) ? ' selected' : '';
|
||||
const done = donePoses.has(a.name) ? ' done' : '';
|
||||
@@ -4803,6 +4877,14 @@
|
||||
renderSidebarGenerate();
|
||||
}
|
||||
|
||||
function sbToggleOrbitAll() {
|
||||
const ORBIT_NAMES = ['Front', '¾ Left', '¾ Right', 'Side L', 'Side R', 'Back'];
|
||||
const allSel = ORBIT_NAMES.every(n => _sbSelectedAngles.has(n));
|
||||
if (allSel) { ORBIT_NAMES.forEach(n => _sbSelectedAngles.delete(n)); }
|
||||
else { ORBIT_NAMES.forEach(n => _sbSelectedAngles.add(n)); }
|
||||
renderSidebarGenerate();
|
||||
}
|
||||
|
||||
function setSbRefMode(mode) {
|
||||
_sbRefMode = mode;
|
||||
renderSidebarGenerate();
|
||||
@@ -5386,6 +5468,8 @@
|
||||
let _sceneVideo = null, _sceneDuration = 0, _sceneFrameBytes = null, _sceneJobPollTimer = null;
|
||||
|
||||
let _sceneGridOpen = false; // whether the video picker grid is expanded
|
||||
let _sceneLibOpen = false; // whether "previous scenery" library panel is expanded
|
||||
let _sceneLib = null; // cached { groups, ungrouped } from /scenery/library
|
||||
// 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),
|
||||
@@ -5518,8 +5602,53 @@
|
||||
${canGen?'':'disabled'}>Create Scenery</button>
|
||||
</div>`;
|
||||
|
||||
// --- Previous scenery library (grouped by source video) ---
|
||||
html += `<div class="sb-sep"></div>
|
||||
<div style="display:flex;align-items:center;gap:6px;margin-bottom:6px">
|
||||
<span class="sb-label" style="margin:0;flex:1">Previous scenery</span>
|
||||
<button class="sb-btn" style="padding:2px 8px;font-size:10px" onclick="sceneLibToggle()">${_sceneLibOpen ? '▲ hide' : '▼ browse'}</button>
|
||||
</div>`;
|
||||
if (_sceneLibOpen) {
|
||||
if (!_sceneLib) {
|
||||
html += `<div id="sceneLibLoader" style="font-size:11px;color:#666;padding:4px 0">Loading…</div>`;
|
||||
} else if (!_sceneLib.groups.length && !_sceneLib.ungrouped.length) {
|
||||
html += `<div style="font-size:11px;color:#555;padding:4px 0">No previous scenery found.</div>`;
|
||||
} else {
|
||||
const renderLibItems = (items) => items.map(it => {
|
||||
const url = IMAGE_FOLDER + encodeURIComponent(it.filename);
|
||||
const gLabel = it.group_id ? ` <span style="color:#555">${escHtml(it.group_id)}</span>` : '';
|
||||
return `<div style="display:inline-block;margin:2px;cursor:pointer;position:relative;vertical-align:top"
|
||||
title="Use as background · ${escHtml(it.filename)}${it.group_id?' ('+escHtml(it.group_id)+')':''}"
|
||||
onclick="sceneLibUseAsBackground(${JSON.stringify(it.filename)})">
|
||||
<img src="${url}" style="width:52px;height:52px;object-fit:cover;border-radius:4px;border:1px solid #333">
|
||||
</div>`;
|
||||
}).join('');
|
||||
_sceneLib.groups.forEach(g => {
|
||||
const vName = g.video.replace(/\.[^.]+$/, '');
|
||||
html += `<div style="margin-bottom:8px">
|
||||
<div style="font-size:10px;color:#888;margin-bottom:3px">📹 ${escHtml(vName)}</div>
|
||||
<div>${renderLibItems(g.items)}</div>
|
||||
</div>`;
|
||||
});
|
||||
if (_sceneLib.ungrouped.length) {
|
||||
html += `<div style="margin-bottom:8px">
|
||||
<div style="font-size:10px;color:#888;margin-bottom:3px">📷 uploaded / other</div>
|
||||
<div>${renderLibItems(_sceneLib.ungrouped)}</div>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
panel.innerHTML = html;
|
||||
|
||||
// Lazy-load library data when panel is opened
|
||||
if (_sceneLibOpen && !_sceneLib) {
|
||||
fetch(`${API}/scenery/library`)
|
||||
.then(r => r.ok ? r.json() : null)
|
||||
.then(d => { if (d) { _sceneLib = d; renderSidebarScenery(); } })
|
||||
.catch(() => {});
|
||||
}
|
||||
|
||||
// Restore the preserved prompt text and size the textarea.
|
||||
const spEl = document.getElementById('scenePromptInput');
|
||||
if (spEl) { spEl.value = prevScenePrompt; autoGrowPrompt(spEl); }
|
||||
@@ -5557,6 +5686,28 @@
|
||||
renderSidebarScenery();
|
||||
}
|
||||
|
||||
function sceneLibToggle() {
|
||||
_sceneLibOpen = !_sceneLibOpen;
|
||||
if (_sceneLibOpen && !_sceneLib) _sceneLib = null; // force re-fetch on open
|
||||
renderSidebarScenery();
|
||||
}
|
||||
|
||||
// Use a library scenery image as slot 0 background (fetched to bytes).
|
||||
async function sceneLibUseAsBackground(filename) {
|
||||
try {
|
||||
const url = IMAGE_FOLDER + encodeURIComponent(filename);
|
||||
const b64 = await _fetchAsBase64(url);
|
||||
_sceneRefs[0] = { kind: 'bytes', data: b64 };
|
||||
_sceneVideo = null;
|
||||
_sceneFrameBytes = null;
|
||||
_sceneGridOpen = false;
|
||||
renderSidebarScenery();
|
||||
showToast('Background set from previous scenery', 'success');
|
||||
} catch (e) {
|
||||
showToast('Could not load image: ' + e, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// ---- scenery reference-slot helpers ----
|
||||
|
||||
// Thumbnail (URL or data-URL) for a slot ref.
|
||||
|
||||
Reference in New Issue
Block a user