3D image pipeline: TripoSR + Blender + ComfyUI integration
Image Generation: - Three-tier visual generation: Illustrate (ComfyUI ~8s), 3D Render (Blender ~20s), AI→3D Sculpt (TripoSR ~60s) - TripoSR image-to-3D mesh pipeline: ComfyUI generates concept → TripoSR creates 16K+ vertex mesh → Blender renders gold sculpture - Blender 4.3.2 with EEVEE renderer, 12 procedural scene types (nautilus, clockwork, neural mesh, fractal tree, Möbius, cathedral, tesseract, wave field, lattice cage, bridge, galaxy, toroidal knot) - Golden ratio composition: 1280x320 panoramic banners, fibonacci spiral prompts - 50 steps, cfg 8.5, DPM++ 2M Karras for ComfyUI quality - VRAM management: auto-stops ComfyUI for TripoSR, restarts after - Displacement map fallback if TripoSR unavailable - All images cached to disk — instant on repeat UI Fixes: - Mobile responsive: wrapped nav, compact cards, tight spacing - Mermaid.js fully removed (caused visible errors) - One-shot illustrate buttons (no stacking) - Button labels: Illustrate / 3D Render / AI→3D Sculpt - Loading states show pipeline steps Infrastructure: - UFW rules for LAN access on :3500, :3600, :8188 - Boot order: PostgreSQL+Ollama → LLM Team → ComfyUI → imagegen - Demo/showcase mode auto-starts on boot - :5000 stays 127.0.0.1 (nginx proxied) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ae53ffe451
commit
5d1941d280
159
llm_team_ui.py
159
llm_team_ui.py
@ -151,7 +151,7 @@ def _check_high_alert_expiry():
|
||||
# IPs that never get rate-limited (your LAN, localhost)
|
||||
ALLOWLIST_IPS = {"127.0.0.1", "::1", "192.168.1.1"}
|
||||
# Demo mode state — toggled by admin at runtime
|
||||
_demo_mode = {"active": True, "started_by": "profit", "showcase": True}
|
||||
_demo_mode = {"active": True, "started_by": "boot", "showcase": True}
|
||||
|
||||
# Routes that demo users CAN trigger (read-like POSTs — enrichment, self-analysis, team runs)
|
||||
DEMO_ALLOWED_POSTS = {
|
||||
@ -2316,7 +2316,34 @@ HTML = r"""
|
||||
.new-prompt-btn:hover { opacity: 0.85; }
|
||||
/* Theme adjustments for composer */
|
||||
@media (max-width: 900px) { .grid { grid-template-columns: 1fr; } .composer-active .left-scroll { padding: 40px 12px 30px; } }
|
||||
@media (max-width: 768px) { .m-toggle { display: flex; } .m-collapse { display: none !important; } .m-collapse.open { display: block !important; } .composer-active .left-scroll { padding: 30px 10px 20px; } }
|
||||
@media (max-width: 768px) {
|
||||
.m-toggle { display: flex; } .m-collapse { display: none !important; } .m-collapse.open { display: block !important; }
|
||||
.composer-active .left-scroll { padding: 30px 10px 20px; }
|
||||
/* Mobile header — compact, wrap nav */
|
||||
header { flex-wrap: wrap; gap: 8px; padding: 10px 0; }
|
||||
header h1 { font-size: 16px; }
|
||||
header .badge { font-size: 8px; padding: 2px 8px; }
|
||||
header nav { width: 100%; gap: 3px !important; flex-wrap: wrap; }
|
||||
header nav a, header nav button, .layout-toggle-btn, .new-prompt-btn { font-size: 8px !important; padding: 3px 6px !important; letter-spacing: 0 !important; }
|
||||
.new-prompt-btn { padding: 3px 8px !important; font-size: 8px !important; }
|
||||
/* Mobile output — tighter spacing */
|
||||
.container { padding: 8px 4px !important; }
|
||||
.container.output-focused { padding: 0 2px !important; }
|
||||
.output-area { gap: 2px !important; max-height: calc(100vh - 60px) !important; }
|
||||
.output-card .card-header { padding: 6px 10px; font-size: 10px; }
|
||||
.card-body.md-rendered { padding: 10px 12px; font-size: 12px; }
|
||||
/* Mobile card actions — wrap to 2 rows */
|
||||
.card-actions { flex-wrap: wrap; gap: 3px; padding: 4px 10px 6px; }
|
||||
.card-act { font-size: 8px; padding: 2px 6px; }
|
||||
/* Mobile progress panel */
|
||||
.progress-panel { width: calc(100% - 10px) !important; max-width: none !important; top: 40px !important; left: 5px !important; transform: none !important; }
|
||||
/* Mobile prompt area */
|
||||
.prompt-area { min-height: 60px; font-size: 12px; }
|
||||
.prompt-metrics { font-size: 8px; }
|
||||
.sample-chip { font-size: 10px; padding: 4px 8px; }
|
||||
/* Mobile hero images */
|
||||
.card-hero-wrap img { height: auto !important; }
|
||||
}
|
||||
.card-actions { display: flex; gap: 4px; padding: 6px 14px 10px; }
|
||||
.card-act { background: none; border: 2px solid var(--border); border-radius: 2px; color: var(--text2); font-size: 9px; padding: 3px 10px; cursor: pointer; transition: all 0.15s; font-family: 'JetBrains Mono', monospace; text-transform: uppercase; letter-spacing: 0.5px; }
|
||||
.card-act:hover { border-color: var(--accent); color: var(--accent); }
|
||||
@ -4117,31 +4144,33 @@ function generateCardImage(cardEl, text) {
|
||||
var clean = text.substring(0, 500).replace(/[#*_`\[\]\n]/g, ' ').replace(/\s+/g, ' ').trim();
|
||||
var keywords = clean.split(' ').filter(function(w) { return w.length > 5; }).slice(0, 5).join(', ');
|
||||
// Force abstract — NEVER attempt realism, people, faces, hands, food
|
||||
// Rotate through high-quality abstract styles for visual variety
|
||||
// Golden ratio composition + high-quality abstract styles
|
||||
// Ultra-wide panoramic banner styles — 4:1 ratio, golden ratio composition
|
||||
var styles = [
|
||||
'geometric wireframe structure dissolving into glowing particles, cinematic macro photography',
|
||||
'flowing golden light streams through crystalline lattice, long exposure photography',
|
||||
'topographic data landscape with luminous contour ridges, scientific visualization',
|
||||
'neural constellation network with radiant synaptic pathways, deep space macro',
|
||||
'molten gold fluid dynamics frozen in time, abstract sculpture photography',
|
||||
'fractal architecture of interconnected golden bridges, aerial perspective',
|
||||
'bioluminescent circuit pathways on obsidian surface, electron microscope aesthetic',
|
||||
'golden aurora threads weaving through dark geometric voids, astrophotography',
|
||||
'panoramic golden wireframe dissolving left to right into luminous particles, sweeping horizontal flow',
|
||||
'ultra-wide crystalline lattice with golden light refracting across dark horizon, fibonacci spiral at left third',
|
||||
'panoramic topographic data landscape, amber contour ridges stretching across dark terrain, depth gradient left to right',
|
||||
'wide-angle neural constellation, golden nodes clustered at right third with connections spanning full width',
|
||||
'panoramic molten gold fluid ribbon flowing horizontally, frozen dynamics on obsidian surface, macro detail',
|
||||
'ultra-wide fractal golden architecture receding into dark vanishing point at right third, perspective depth',
|
||||
'panoramic bioluminescent circuit traces flowing horizontally across obsidian, bright cluster at golden ratio point',
|
||||
'wide-angle golden aurora filaments sweeping across dark void, dense light at left transitioning to negative space',
|
||||
];
|
||||
var style = styles[Math.floor(Math.random() * styles.length)];
|
||||
var imagePrompt = style + ', dark background, gold and amber tones, '
|
||||
var imagePrompt = style + ', ultra-wide panoramic banner, dark background, gold and amber tones, '
|
||||
+ 'golden ratio composition, horizontal flow, asymmetric balance, '
|
||||
+ 'no people, no faces, no hands, no text, no letters, '
|
||||
+ 'sharp focus, 8k detail, editorial magazine quality, thematic: ' + keywords;
|
||||
|
||||
loading.textContent = 'Generating...';
|
||||
loading.textContent = 'Rendering high-quality illustration (up to 30s)...';
|
||||
fetch('/api/imagegen', {
|
||||
method: 'POST', headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({prompt: imagePrompt, width: 1024, height: 512, steps: 8})
|
||||
body: JSON.stringify({prompt: imagePrompt, width: 1280, height: 320, steps: 50})
|
||||
}).then(function(r) { return r.json(); }).then(function(data) {
|
||||
if (data.image) {
|
||||
wrap.textContent = '';
|
||||
var img = document.createElement('img');
|
||||
img.style.cssText = 'width:100%;height:160px;object-fit:cover;display:block';
|
||||
img.style.cssText = 'width:100%;display:block';
|
||||
img.src = 'data:image/webp;base64,' + data.image;
|
||||
wrap.appendChild(img);
|
||||
var label = document.createElement('div'); label.className = 'card-hero-label';
|
||||
@ -4155,16 +4184,84 @@ function generateCardImage(cardEl, text) {
|
||||
function addIllustrateBtn(cardEl, text) {
|
||||
var actions = cardEl.querySelector('.card-actions');
|
||||
if (!actions) return;
|
||||
var voteBtn = actions.querySelector('.vote-btn');
|
||||
// AI Illustrate button
|
||||
var btn = document.createElement('button'); btn.className = 'card-act';
|
||||
btn.textContent = 'Illustrate';
|
||||
btn.onclick = function(e) {
|
||||
e.stopPropagation();
|
||||
btn.textContent = 'Generating...';
|
||||
btn.disabled = true;
|
||||
if (cardEl.querySelector('.card-hero-wrap')) return;
|
||||
btn.textContent = 'Rendering...'; btn.disabled = true;
|
||||
generateCardImage(cardEl, text);
|
||||
setTimeout(function() { btn.textContent = 'Illustrate'; btn.disabled = false; }, 5000);
|
||||
};
|
||||
actions.insertBefore(btn, actions.querySelector('.vote-btn') || null);
|
||||
actions.insertBefore(btn, voteBtn || null);
|
||||
// Blender 3D button
|
||||
var btn3d = document.createElement('button'); btn3d.className = 'card-act';
|
||||
btn3d.style.cssText = 'color:var(--accent);border-color:rgba(226,181,90,0.3)';
|
||||
btn3d.textContent = '3D Render';
|
||||
btn3d.onclick = function(e) {
|
||||
e.stopPropagation();
|
||||
if (cardEl.querySelector('.card-hero-wrap')) return;
|
||||
btn3d.textContent = 'Rendering 3D (~60s)...'; btn3d.disabled = true;
|
||||
var wrap = document.createElement('div'); wrap.className = 'card-hero-wrap';
|
||||
var loading = document.createElement('div'); loading.className = 'card-hero-loading';
|
||||
loading.textContent = 'Ray-tracing 3D scene with Blender Cycles...';
|
||||
wrap.appendChild(loading);
|
||||
var body = cardEl.querySelector('.card-body');
|
||||
if (body) body.parentNode.insertBefore(wrap, body);
|
||||
var seed = Math.floor(Math.random() * 99999);
|
||||
fetch('/api/imagegen/blender', {
|
||||
method: 'POST', headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({seed: seed})
|
||||
}).then(function(r) { return r.json(); }).then(function(data) {
|
||||
if (data.image) {
|
||||
wrap.textContent = '';
|
||||
var img = document.createElement('img');
|
||||
img.style.cssText = 'width:100%;display:block';
|
||||
img.src = 'data:image/webp;base64,' + data.image;
|
||||
wrap.appendChild(img);
|
||||
var label = document.createElement('div'); label.className = 'card-hero-label';
|
||||
label.textContent = 'blender cycles 3d | seed ' + seed + ' | ' + (data.time_ms ? Math.round(data.time_ms/1000) + 's' : 'cached');
|
||||
wrap.appendChild(label);
|
||||
} else { wrap.remove(); btn3d.textContent = '3D Render'; btn3d.disabled = false; }
|
||||
}).catch(function() { wrap.remove(); btn3d.textContent = '3D Render'; btn3d.disabled = false; });
|
||||
};
|
||||
actions.insertBefore(btn3d, voteBtn || null);
|
||||
// AI → 3D Relief button
|
||||
var btn3dr = document.createElement('button'); btn3dr.className = 'card-act';
|
||||
btn3dr.style.cssText = 'color:var(--green);border-color:rgba(74,222,128,0.3)';
|
||||
btn3dr.textContent = 'AI\u21923D Sculpt';
|
||||
btn3dr.title = 'TripoSR: AI generates image → converts to 3D mesh → Blender renders as gold sculpture (~60s)';
|
||||
btn3dr.onclick = function(e) {
|
||||
e.stopPropagation();
|
||||
if (cardEl.querySelector('.card-hero-wrap')) return;
|
||||
btn3dr.textContent = 'Sculpting (~60s)...'; btn3dr.disabled = true;
|
||||
var wrap = document.createElement('div'); wrap.className = 'card-hero-wrap';
|
||||
var loading = document.createElement('div'); loading.className = 'card-hero-loading';
|
||||
loading.textContent = 'Step 1/3: AI generating concept → Step 2: TripoSR 3D mesh → Step 3: Blender gold render...';
|
||||
wrap.appendChild(loading);
|
||||
var body = cardEl.querySelector('.card-body');
|
||||
if (body) body.parentNode.insertBefore(wrap, body);
|
||||
var keywords = text.substring(0, 300).replace(/[#*_`\[\]\n]/g, ' ').replace(/\s+/g, ' ').trim();
|
||||
var kw = keywords.split(' ').filter(function(w){return w.length > 5}).slice(0, 4).join(', ');
|
||||
var imgPrompt = 'abstract conceptual art, ' + kw + ', golden energy flows, dark background, sharp fractal detail, no people no text';
|
||||
fetch('/api/imagegen/img-to-3d', {
|
||||
method: 'POST', headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({prompt: imgPrompt, seed: Math.floor(Math.random() * 99999)})
|
||||
}).then(function(r) { return r.json(); }).then(function(data) {
|
||||
if (data.image) {
|
||||
wrap.textContent = '';
|
||||
var img = document.createElement('img');
|
||||
img.style.cssText = 'width:100%;display:block';
|
||||
img.src = 'data:image/webp;base64,' + data.image;
|
||||
wrap.appendChild(img);
|
||||
var label = document.createElement('div'); label.className = 'card-hero-label';
|
||||
label.textContent = 'ai \u2192 3d gold relief | ' + (data.time_ms ? Math.round(data.time_ms/1000) + 's' : 'cached');
|
||||
wrap.appendChild(label);
|
||||
} else { wrap.remove(); btn3dr.textContent = 'AI\u21923D Sculpt'; btn3dr.disabled = false; }
|
||||
}).catch(function() { wrap.remove(); btn3dr.textContent = 'AI\u21923D Sculpt'; btn3dr.disabled = false; });
|
||||
};
|
||||
actions.insertBefore(btn3dr, voteBtn || null);
|
||||
}
|
||||
|
||||
// ─── CARD ACTIONS ────────────────────────────────────
|
||||
@ -6780,6 +6877,30 @@ def proxy_imagegen():
|
||||
return jsonify({"error": str(e)})
|
||||
|
||||
|
||||
@app.route("/api/imagegen/blender", methods=["POST"])
|
||||
@login_required
|
||||
def proxy_blender():
|
||||
"""Proxy Blender 3D render to :3600."""
|
||||
try:
|
||||
resp = requests.post("http://localhost:3600/blender",
|
||||
json=request.json, timeout=300)
|
||||
return jsonify(resp.json())
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)})
|
||||
|
||||
|
||||
@app.route("/api/imagegen/img-to-3d", methods=["POST"])
|
||||
@login_required
|
||||
def proxy_img_to_3d():
|
||||
"""AI image → 3D gold relief → Blender render pipeline."""
|
||||
try:
|
||||
resp = requests.post("http://localhost:3600/img-to-3d",
|
||||
json=request.json, timeout=300)
|
||||
return jsonify(resp.json())
|
||||
except Exception as e:
|
||||
return jsonify({"error": str(e)})
|
||||
|
||||
|
||||
@app.route("/api/admin/model-timeouts")
|
||||
@admin_required
|
||||
def admin_model_timeouts():
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user