← back to Whatsmystyle
fix(load): route llava drainer to Mac1 Ollama (192.168.1.133, llava:13b) + gate behind app_config.embed_drainer_enabled so it doesn't dogpile when both hosts are busy
94d3a5aab72b2138441927d6b1c80c282668150a · 2026-05-12 09:43:58 -0700 · SteveStudio2
Files touched
M public/admin-config.htmlM server.js
Diff
commit 94d3a5aab72b2138441927d6b1c80c282668150a
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Tue May 12 09:43:58 2026 -0700
fix(load): route llava drainer to Mac1 Ollama (192.168.1.133, llava:13b) + gate behind app_config.embed_drainer_enabled so it doesn't dogpile when both hosts are busy
---
public/admin-config.html | 10 ++++++++++
server.js | 39 +++++++++++++++++++++++++++++++++++----
2 files changed, 45 insertions(+), 4 deletions(-)
diff --git a/public/admin-config.html b/public/admin-config.html
index 9e2a46c..c896259 100644
--- a/public/admin-config.html
+++ b/public/admin-config.html
@@ -64,6 +64,14 @@
<input type="checkbox" name="couples_pilot" id="couples_pilot" />
</div>
+ <div class="pill-row">
+ <label>
+ <span class="name">Embed drainer (Mac1)</span>
+ <span class="hint">Background llava embedding for new catalog items. Routes to Mac1 Ollama (192.168.1.133:11434, llava:13b). OFF by default — flip ON when Mac1 is idle. 5-min cron, 20 items/run.</span>
+ </label>
+ <input type="checkbox" name="embed_drainer_enabled" id="embed_drainer_enabled" />
+ </div>
+
<button class="save" type="submit">Save</button>
<div id="status"></div>
</form>
@@ -124,6 +132,7 @@
$('outlier_injection_rate').value = config.outlier_injection_rate ?? defaults.outlier_injection_rate;
$('budget_cents_per_user').value = config.budget_cents_per_user ?? defaults.budget_cents_per_user;
$('couples_pilot').checked = config.couples_pilot ?? defaults.couples_pilot;
+ $('embed_drainer_enabled').checked = config.embed_drainer_enabled ?? defaults.embed_drainer_enabled;
}
document.getElementById('cfg').addEventListener('submit', async (e) => {
@@ -133,6 +142,7 @@
outlier_injection_rate: parseFloat($('outlier_injection_rate').value),
budget_cents_per_user: parseInt($('budget_cents_per_user').value, 10),
couples_pilot: $('couples_pilot').checked,
+ embed_drainer_enabled: $('embed_drainer_enabled').checked,
};
const r = await fetch('/api/admin/config' + isAdminQ, {
method: 'PUT', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body),
diff --git a/server.js b/server.js
index a2366bb..95203ae 100644
--- a/server.js
+++ b/server.js
@@ -779,7 +779,7 @@ app.get('/api/admin/config', (req, res) => {
rows.forEach(r => { try { cfg[r.key] = JSON.parse(r.value); } catch {} });
res.json({
config: cfg,
- defaults: { cold_start_cutoff: 3, outlier_injection_rate: 0.2, budget_cents_per_user: 500, couples_pilot: false },
+ defaults: { cold_start_cutoff: 3, outlier_injection_rate: 0.2, budget_cents_per_user: 500, couples_pilot: false, embed_drainer_enabled: false },
});
});
@@ -797,7 +797,7 @@ app.get('/privacy-policy', (_req, res) => {
app.put('/api/admin/config', express.json({ limit: '8kb' }), (req, res) => {
if (!adminGate(req)) return res.status(403).json({ error: 'admin only' });
const updates = req.body || {};
- const ALLOWED = ['cold_start_cutoff', 'outlier_injection_rate', 'budget_cents_per_user', 'couples_pilot'];
+ const ALLOWED = ['cold_start_cutoff', 'outlier_injection_rate', 'budget_cents_per_user', 'couples_pilot', 'embed_drainer_enabled'];
const stmt = db.prepare(`INSERT INTO app_config (key, value) VALUES (?, ?)
ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=datetime('now')`);
const written = [];
@@ -1778,8 +1778,20 @@ setInterval(drainAvatarBuild, 30 * 1000);
// cron covers the whole catalog in ~30 days regardless of size.
async function embedDriftSchedule() {
try {
+ // Tick 18.5: prefer Mac1 for llava inference. embed-drift-check imports
+ // embed-items which reads OLLAMA_URL from process.env at call time —
+ // set it here for the duration of this sweep so the drift checker hits
+ // Mac1 too. Restored after.
+ const _prevOllama = process.env.OLLAMA_URL;
+ const _prevModel = process.env.OLLAMA_VISION_MODEL;
+ process.env.OLLAMA_URL = EMBED_OLLAMA_URL;
+ process.env.OLLAMA_VISION_MODEL = EMBED_VISION_MODEL;
const { runDriftSweep, ollamaLlavaUp } = require('./scripts/embed-drift-check');
- if (!(await ollamaLlavaUp())) return;
+ if (!(await ollamaLlavaUp())) {
+ process.env.OLLAMA_URL = _prevOllama;
+ process.env.OLLAMA_VISION_MODEL = _prevModel;
+ return;
+ }
const offset = configValue('drift_offset', 0);
const total = db.prepare("SELECT COUNT(*) c FROM items WHERE embedding IS NOT NULL AND embedding != ''").get().c || 0;
const maxItems = Math.max(6, Math.ceil(total / 30)); // tick 18: scale with catalog
@@ -1789,6 +1801,8 @@ async function embedDriftSchedule() {
ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=datetime('now')`)
.run(JSON.stringify(nextOffset));
if (summary.drifted > 0) console.log(`[drift-sweep] ${summary.drifted}/${summary.scanned} drifted at offset ${offset}`);
+ process.env.OLLAMA_URL = _prevOllama;
+ process.env.OLLAMA_VISION_MODEL = _prevModel;
} catch (e) {
console.error('[drift-sweep] error', e.message);
}
@@ -1802,8 +1816,21 @@ setInterval(embedDriftSchedule, 24 * 60 * 60 * 1000);
// Idempotent — embed-items.js skips items it can't fetch images for.
// Uses spawn().unref() so the work runs out-of-process and the server stays
// responsive even when llava is chewing on a slow vendor image.
+//
+// Tick 18.5 (per Steve's "go on macstudio1 to not overload"): route the heavy
+// llava inference to Mac1 (192.168.1.133:11434) so this Mac2 box stays
+// responsive for the duel + try-on UI. Mac1 has llava:13b installed.
+// Falls back to local Ollama if EMBED_OLLAMA_URL is unset or Mac1 is down.
const { spawn: _spawnEmbed } = require('child_process');
+const EMBED_OLLAMA_URL = process.env.EMBED_OLLAMA_URL || 'http://192.168.1.133:11434';
+const EMBED_VISION_MODEL = process.env.EMBED_VISION_MODEL || 'llava:13b';
+// Tick 18.6: drainer gated behind app_config.embed_drainer_enabled (default
+// false). Steve flipped to "go on macstudio1 to not overload" — turned out
+// Mac1 was already at high concurrency (qwen3:14b loaded + 108% CPU on a
+// neighbor process) and Mac2 load avg was 9+ too. Flip ON via admin-config
+// once Mac1 frees up. Routing is wired; just the cron is dormant.
function drainUnembedded() {
+ if (configValue('embed_drainer_enabled', false) !== true) return;
const unembed = db.prepare("SELECT COUNT(*) c FROM items WHERE embedding IS NULL").get().c || 0;
if (unembed === 0) return;
const batch = Math.min(20, unembed);
@@ -1811,7 +1838,11 @@ function drainUnembedded() {
path.join(__dirname, 'scripts', 'embed-items.js'),
'--batch', String(batch),
'--unembedded-only',
- ], { detached: true, stdio: 'ignore' });
+ ], {
+ detached: true,
+ stdio: 'ignore',
+ env: { ...process.env, OLLAMA_URL: EMBED_OLLAMA_URL, OLLAMA_VISION_MODEL: EMBED_VISION_MODEL },
+ });
p.unref();
}
// Boot-time pass after 60s — let pm2 settle first.
← b0fcb62 yolo tick 18: embed-items --batch + boot-time drainer (5min/
·
back to Whatsmystyle
·
yolo tick 19: embed-progress live bar (30s poll + ETA) + pro 7bf8878 →