diff --git a/llm_team_ui.py b/llm_team_ui.py index bb4c3f7..7dc9a03 100644 --- a/llm_team_ui.py +++ b/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():