botui/ui/suite/partials/vibe.html

874 lines
46 KiB
HTML
Raw Normal View History

2026-02-26 12:40:44 -03:00
<!-- Vibe Window — APP_CREATE Canvas + Agents IDE (Phase 7) -->
<link rel="stylesheet" href="/suite/vibe/agents-sidebar.css" />
<div class="vibe-container" id="vibeWindow">
<!-- Pipeline Tabs -->
<div class="vibe-pipeline">
<button class="vibe-pipeline-tab" data-stage="plan">// PLAN</button>
<button class="vibe-pipeline-tab active" data-stage="build">// BUILD</button>
<button class="vibe-pipeline-tab" data-stage="review">// REVIEW</button>
<button class="vibe-pipeline-tab" data-stage="deploy">// DEPLOY</button>
<button class="vibe-pipeline-tab" data-stage="monitor">// MONITOR</button>
</div>
2026-02-26 12:40:44 -03:00
<!-- Main Content: Canvas + Sidebar -->
<div class="vibe-body">
<!-- Agents & Workspaces Sidebar (Fixed to left as in image) -->
<aside class="agents-sidebar" id="agentsSidebar" style="background: var(--surface, #fcfcfc);">
<div class="as-logo-section"
style="padding: 16px; border-bottom: 1px solid var(--border, #f0f1f2); display: flex; align-items: center; justify-content: space-between;">
<h2
style="margin:0; font-size: 15px; color: var(--text, #3b3b3b); font-weight: 800; display: flex; align-items: center; gap: 8px;">
<span style="font-size: 18px; color: var(--accent, #84d669);">🌱</span> mantis farm
</h2>
<div style="display:flex; gap: 12px; color: var(--text-muted, #888); font-size: 14px;">
<span style="cursor:pointer;" title="Home"></span>
<span style="cursor:pointer;" title="Overview"></span>
2026-02-26 12:40:44 -03:00
</div>
</div>
2026-02-26 12:40:44 -03:00
<div class="as-section">
<div class="as-section-header">
<h3>Agents</h3>
<button class="as-collapse-btn" id="agentsSidebarCollapse" type="button"></button>
</div>
<div class="as-agent-list" id="asAgentList">
<!-- Mantis #1 EVOLVED -->
<div class="as-agent-card" data-agent-id="1" style="border-left: 3px solid var(--accent, #84d669);">
<div class="as-agent-header">
<span class="as-status-dot green"></span>
<span class="as-agent-name">Mantis #1</span>
<span class="as-drag-handle" style="margin-left:auto;"></span>
</div>
<div class="as-agent-body">
<span class="as-agent-icons">👀 ⚙️ ⚡</span>
<span class="as-badge badge-evolved">EVOLVED</span>
</div>
</div>
<!-- Mantis #2 BRED -->
<div class="as-agent-card" data-agent-id="2">
<div class="as-agent-header">
<span class="as-status-dot yellow"></span>
<span class="as-agent-name">Mantis #2</span>
<span class="as-drag-handle" style="margin-left:auto;"></span>
</div>
<div class="as-agent-body">
<span class="as-agent-icons">🥚</span>
<span class="as-badge badge-bred">BRED</span>
</div>
</div>
<!-- Mantis #3 & #4 WILD -->
<div class="as-agent-card" style="opacity: 0.6;" data-agent-id="3">
<div class="as-agent-header">
<span class="as-status-dot gray"></span>
<span class="as-agent-name">Mantis #3</span>
<span class="as-drag-handle" style="margin-left:auto;"></span>
</div>
<div class="as-agent-body">
<span class="as-agent-icons" style="filter: grayscale(1);">🥚</span>
<span class="as-badge badge-wild" style="background: var(--surface-active, #ccc)">WILD</span>
</div>
</div>
<div class="as-agent-card" style="opacity: 0.6;" data-agent-id="4">
<div class="as-agent-header">
<span class="as-status-dot gray"></span>
<span class="as-agent-name">Mantis #4</span>
<span class="as-drag-handle" style="margin-left:auto;"></span>
</div>
<div class="as-agent-body">
<span class="as-agent-icons" style="filter: grayscale(1);">🥚</span>
<span class="as-badge badge-wild" style="background: var(--surface-active, #ccc)">WILD</span>
</div>
2026-02-26 12:40:44 -03:00
</div>
</div>
<div style="padding: 0 8px;">
<button class="as-create-btn" id="createAgentBtn" type="button"
style="width: calc(100% - 16px); margin: 8px;">+ Create a New Mantis</button>
</div>
2026-02-26 12:40:44 -03:00
</div>
<div class="as-section">
<div class="as-section-header">
<h3>Workspaces</h3>
</div>
<div class="as-workspace-list" id="asWorkspaceList">
<div class="as-workspace-item">
<button class="as-workspace-toggle" type="button"
style="background: var(--bg, #fff); border-left: 3px solid var(--accent, #84d669);">
<span class="as-workspace-arrow"></span>
<span>E-Commerce App Development</span>
2026-02-26 12:40:44 -03:00
</button>
<div class="as-workspace-body" style="display:block;">
<div class="as-workspace-agent">Mantis #1</div>
<div class="as-workspace-agent">Mantis #4</div>
<div class="as-workspace-dropzone" data-workspace="ecommerce">
Drag a Mantis to Include
2026-02-26 12:40:44 -03:00
</div>
</div>
</div>
<div class="as-workspace-item">
<button class="as-workspace-toggle" type="button">
<span class="as-workspace-arrow"></span>
<span>Accountability App Development</span>
2026-02-26 12:40:44 -03:00
</button>
<div class="as-workspace-body" style="display:none;">
<div class="as-workspace-agent">Mantis #4</div>
<div class="as-workspace-dropzone" data-workspace="accountability">
Drag a Mantis to Include
2026-02-26 12:40:44 -03:00
</div>
</div>
</div>
</div>
<div style="padding: 0 8px;">
<button class="as-create-btn" id="createWorkspaceBtn" type="button"
style="width: calc(100% - 16px); margin: 8px;">+ Create a New Project</button>
</div>
2026-02-26 12:40:44 -03:00
</div>
</aside>
<!-- Canvas Area -->
<div class="vibe-canvas" id="vibeCanvas"
style="background: var(--bg, #fdfdfd); background-image: radial-gradient(var(--border, #e0e0e0) 1px, transparent 1px); background-size: 20px 20px; position:relative;">
<div
style="padding: 16px 24px; font-size: 11px; color: var(--text-muted, #888); font-weight: 600; letter-spacing: 0.5px; border-bottom: 1px solid var(--border, #f0f1f2); background: rgba(255,255,255,0.8); backdrop-filter: blur(4px);">
// DASHBOARD <span style="color: var(--text-secondary, #ccc); margin:0 6px;">&gt;</span> // E-COMMERCE APP DEVELOPMENT
<div style="float: right;">
<button
style="border: 1px solid var(--border, #e0e0e0); background: var(--bg, #fff); border-radius: 4px; padding: 2px 8px; cursor:pointer;">-</button>
<span style="font-size: 11px; margin: 0 8px; color: var(--text, #333);">100%</span>
<button
style="border: 1px solid var(--border, #e0e0e0); background: var(--bg, #fff); border-radius: 4px; padding: 2px 8px; cursor:pointer;">+</button>
</div>
</div>
<!-- Steps Nodes (populated dynamically from chat/API) -->
<div class="vibe-steps" id="vibeSteps"
style="padding: 40px; display: none; gap: 60px; align-items: flex-start; overflow-x: auto;">
</div>
<!-- Empty state -->
<div id="vibeCanvasEmpty"
style="flex:1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 16px; padding: 60px; text-align: center;">
<div style="font-size: 56px; animation: float 3s ease-in-out infinite;">🌱</div>
<h3
style="margin: 0; font-size: 22px; font-weight: 800; color: var(--text, #3b3b3b); font-family: 'Fira Code', monospace;">
Vibe — App Builder</h3>
<p style="margin: 0; font-size: 14px; color: var(--text-muted, #888); max-width: 440px; line-height: 1.6;">
Describe what you want to build in the chat. Mantis #1 will analyze your request, generate task
nodes on this canvas, and build the entire application for you.
</p>
<div style="display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; margin-top: 12px;">
<button class="vibe-quick-btn" type="button"
onclick="document.getElementById('vibeChatInput').value='Create an e-commerce app for selling handmade crafts with shopping cart and payments'; document.getElementById('vibeChatInput').focus();"
style="padding: 8px 16px; border: 1px solid var(--border, #e0e0e0); border-radius: 20px; background: var(--bg, #fff); font-size: 12px; cursor: pointer; color: var(--text-muted, #666); transition: all 0.15s; font-family: 'Fira Code', monospace;">
🛍️ E-Commerce App
</button>
<button class="vibe-quick-btn" type="button"
onclick="document.getElementById('vibeChatInput').value='Build a CRM system with contacts, leads, and deal pipeline tracking'; document.getElementById('vibeChatInput').focus();"
style="padding: 8px 16px; border: 1px solid var(--border, #e0e0e0); border-radius: 20px; background: var(--bg, #fff); font-size: 12px; cursor: pointer; color: var(--text-muted, #666); transition: all 0.15s; font-family: 'Fira Code', monospace;">
📇 CRM System
</button>
<button class="vibe-quick-btn" type="button"
onclick="document.getElementById('vibeChatInput').value='Create a project management dashboard with tasks, Kanban board, and team assignments'; document.getElementById('vibeChatInput').focus();"
style="padding: 8px 16px; border: 1px solid var(--border, #e0e0e0); border-radius: 20px; background: var(--bg, #fff); font-size: 12px; cursor: pointer; color: var(--text-muted, #666); transition: all 0.15s; font-family: 'Fira Code', monospace;">
📊 Project Manager
</button>
</div>
</div>
<!-- Vibe Chat Overlay (LIVE) -->
<div id="vibeChatOverlay"
style="position: absolute; bottom: 24px; right: 24px; width: 380px; background: var(--surface, #1a1a24); border-radius: 12px; box-shadow: 0 8px 24px rgba(0,0,0,0.2); display: flex; flex-direction: column; overflow: hidden; border: 1px solid var(--border, #2a2a35); color: var(--bg, #fff); z-index: 100;">
<div
style="padding: 12px 16px; background: var(--surface-hover, #23232f); border-bottom: 1px solid var(--border, #2a2a35); display: flex; justify-content: space-between; align-items: center;">
<div style="display: flex; align-items: center; gap: 8px;">
<span class="as-status-dot green" id="vibeChatStatusDot"
style="box-shadow: 0 0 8px #84d669;"></span>
<span style="font-size: 12px; font-weight: 600;">Mantis #1</span>
</div>
<span id="vibeChatStatusBadge"
style="font-size: 10px; color: var(--text-muted, #888); background: #333; padding: 2px 6px; border-radius: 4px;">CONNECTING…</span>
</div>
<div id="vibeChatMessages"
style="padding: 16px; display: flex; flex-direction: column; gap: 12px; font-size: 12px; line-height: 1.5; min-height: 220px; max-height: 350px; overflow-y: auto; font-family: 'Segoe UI', system-ui, sans-serif;">
<!-- Welcome hint -->
<div
style="align-self: center; background: rgba(132,214,105,0.12); color: var(--accent, #84d669); padding: 8px 14px; border-radius: 8px; font-size: 11px; text-align: center;">
💡 TIP: Describe your project. The more detail, the better the plan.
</div>
</div>
<div style="padding: 12px; border-top: 1px solid #2a2a35; background: var(--surface, #1a1a24);">
<form id="vibeChatForm" autocomplete="off"
style="display: flex; align-items: center; gap: 8px; background: var(--surface-hover, #23232f); padding: 8px 12px; border-radius: 20px; border: 1px solid #333;">
<span style="color: var(--text-muted, #888); cursor: pointer; transform: rotate(-45deg);">📎</span>
<input type="text" id="vibeChatInput" placeholder="Describe your project…"
style="flex: 1; background: transparent; border: none; color: var(--bg, #fff); font-size: 13px; outline: none; font-family: 'Segoe UI', system-ui, sans-serif;" />
<button type="submit" id="vibeChatSend"
style="background: #84d669; color: #1a1a24; width: 24px; height: 24px; border-radius: 50%; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 14px; font-weight: bold; border: none;">↑</button>
</form>
</div>
</div>
<!-- Preview Panel (hidden by default) -->
<div class="vibe-preview" id="vibePreview" style="display:none;">
<div class="vibe-preview-header">
<span>// PREVIEW</span>
<input type="text" class="vibe-preview-url" id="vibePreviewUrl" value="" readonly />
</div>
<div class="vibe-preview-content" id="vibePreviewContent"></div>
</div>
</div>
</div>
</div>
<script>
(function () {
"use strict";
/* ── state ── */
var vibeWs = null;
var vibeSessionId = null;
var vibeUserId = null;
var vibeBotId = "default";
var vibeBotName = "default";
var vibeStreaming = false;
var vibeStreamId = null;
var vibeStreamContent = "";
var taskNodes = [];
var currentProject = "My App";
var nodeIdCounter = 0;
/* ── helpers ── */
function esc(text) {
var d = document.createElement("div");
d.textContent = text || "";
return d.innerHTML;
}
function vibeAddMsg(role, text) {
var box = document.getElementById("vibeChatMessages");
if (!box) return;
var div = document.createElement("div");
if (role === "user") {
div.style.cssText = "align-self:flex-end;background:#84d669;color:#1a1a24;font-weight:500;padding:10px 14px;border-radius:12px 12px 0 12px;max-width:85%;word-wrap:break-word;";
div.textContent = text;
} else if (role === "system") {
div.style.cssText = "align-self:center;background:rgba(132,214,105,0.12);color: var(--accent, #84d669);padding:6px 12px;border-radius:8px;font-size:11px;text-align:center;";
div.innerHTML = text;
} else {
div.style.cssText = "align-self:flex-start;background: var(--border, #2a2a35);color:#ececec;padding:10px 14px;border-radius:12px 12px 12px 0;max-width:85%;word-wrap:break-word;";
div.className = "vibe-bot-msg";
if (typeof marked !== "undefined" && marked.parse) {
div.innerHTML = marked.parse(text);
} else {
div.textContent = text;
}
}
box.appendChild(div);
box.scrollTop = box.scrollHeight;
return div;
}
function vibeAddStreamStart() {
vibeStreamId = "vibe-stream-" + Date.now();
vibeStreamContent = "";
var el = vibeAddMsg("bot", "▍");
if (el) el.id = vibeStreamId;
return el;
}
function vibeUpdateStream(content) {
vibeStreamContent += (content || "");
var el = document.getElementById(vibeStreamId);
if (!el) return;
if (typeof marked !== "undefined" && marked.parse) {
el.innerHTML = marked.parse(vibeStreamContent);
} else {
el.textContent = vibeStreamContent;
}
var box = document.getElementById("vibeChatMessages");
if (box) box.scrollTop = box.scrollHeight;
}
function vibeFinalizeStream() {
var el = document.getElementById(vibeStreamId);
if (el) {
if (typeof marked !== "undefined" && marked.parse) {
el.innerHTML = marked.parse(vibeStreamContent);
} else {
el.textContent = vibeStreamContent;
}
el.removeAttribute("id");
}
vibeStreamId = null;
vibeStreamContent = "";
vibeStreaming = false;
}
/* ── update status badge ── */
function setVibeStatus(status) {
var dot = document.getElementById("vibeChatStatusDot");
var badge = document.getElementById("vibeChatStatusBadge");
if (status === "connected") {
if (dot) { dot.className = "as-status-dot green"; dot.style.boxShadow = "0 0 8px #84d669"; }
if (badge) { badge.textContent = "EVOLVED"; badge.style.background = "#84d669"; badge.style.color = "#fff"; }
} else if (status === "connecting") {
if (dot) { dot.className = "as-status-dot yellow"; dot.style.boxShadow = "0 0 8px #f59e0b"; }
if (badge) { badge.textContent = "CONNECTING…"; badge.style.background = "#333"; badge.style.color = "#888"; }
} else {
if (dot) { dot.className = "as-status-dot red"; dot.style.boxShadow = "0 0 8px #ef4444"; }
if (badge) { badge.textContent = "OFFLINE"; badge.style.background = "#333"; badge.style.color = "#888"; }
}
}
/* ── update agent sidebar card ── */
function updateMantis1(status, detail) {
var card = document.querySelector('.as-agent-card[data-agent-id="1"]');
if (!card) return;
var bar = card.querySelector(".as-agent-bar .as-bar-fill");
if (status === "working") {
card.style.borderLeftColor = "#f59e0b";
if (!card.querySelector(".as-agent-bar")) {
var barWrapper = document.createElement("div");
barWrapper.className = "as-agent-bar";
barWrapper.innerHTML = '<div class="as-bar-fill bred" style="width:0%;transition:width 0.5s;"></div>';
card.appendChild(barWrapper);
}
} else if (status === "done") {
card.style.borderLeftColor = "#84d669";
bar = card.querySelector(".as-bar-fill");
if (bar) bar.style.width = "100%";
setTimeout(function () {
var b = card.querySelector(".as-agent-bar");
if (b) b.remove();
}, 2000);
}
}
/* ── create task node on canvas ── */
function addTaskNode(title, description, meta) {
var stepsContainer = document.getElementById("vibeSteps");
if (!stepsContainer) return;
stepsContainer.style.display = "flex";
var emptyState = document.getElementById("vibeCanvasEmpty");
if (emptyState) emptyState.style.display = "none";
nodeIdCounter++;
meta = meta || {};
var fileCount = meta.estimated_files || meta.files || Math.floor(Math.random() * 15 + 3);
var time = meta.estimated_time || meta.time || Math.floor(Math.random() * 20 + 5) + "m";
var tokens = meta.estimated_tokens || meta.tokens || "~" + Math.floor(Math.random() * 30 + 10) + "k tokens";
var status = meta.status || "Planning";
var fileList = meta.fileList || [];
var isFirst = stepsContainer.children.length === 0;
var nodeId = "vibe-node-" + nodeIdCounter;
var statusBg = status === "Done" ? "#84d669" : (status === "Planning" ? "#eef8eb" : "#fff3cd");
var statusColor = status === "Done" ? "#fff" : (status === "Planning" ? "#84d669" : "#856404");
var subTasksHtml = "";
if (fileList.length > 0) {
subTasksHtml = '<div id="' + nodeId + '-files" style="display:none;padding:8px 16px;border-top:1px solid #f0f1f2;font-size:10px;color:#555;">';
for (var fi = 0; fi < fileList.length; fi++) {
subTasksHtml += '<div style="padding:2px 0;display:flex;align-items:center;gap:4px;"><span style="color: var(--accent, #84d669);">📄</span> ' + esc(fileList[fi]) + '</div>';
}
subTasksHtml += '</div>';
}
var node = document.createElement("div");
node.className = "vibe-task-node";
node.style.cssText = "background: var(--bg, #fff);border:" + (isFirst ? "2px solid #84d669" : "1px solid #f0f1f2") + ";border-radius:8px;width:280px;box-shadow:0 " + (isFirst ? "4" : "2") + "px 12px rgba(" + (isFirst ? "132,214,105,0.15" : "0,0,0,0.05") + ");position:relative;flex-shrink:0;animation:nodeIn 0.4s ease;";
node.innerHTML =
'<div style="padding:12px 16px;border-bottom: 1px solid var(--border, #f0f1f2);">' +
'<div style="display:flex;justify-content:space-between;margin-bottom:8px;font-size:10px;color: var(--text-muted, #888);">' +
'<span>' + fileCount + ' files</span><span>' + time + '</span><span>' + tokens + '</span>' +
'</div>' +
'<h4 style="margin:0 0 8px 0;font-size:14px;color: var(--text, #3b3b3b);font-weight:700;">' + esc(title) + '</h4>' +
'<p style="margin:0;font-size:11px;color: var(--text-muted, #666);line-height:1.4;">' + esc(description) + '</p>' +
'</div>' +
'<div style="padding:10px 16px;background: var(--surface, #fcfcfc);border-bottom: 1px solid var(--border, #f0f1f2);font-size:11px;">' +
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">' +
'<span style="color: var(--text-muted, #888);">Status</span>' +
'<span style="background:' + statusBg + ';color:' + statusColor + ';padding:2px 8px;border-radius:12px;font-weight:600;">' + esc(status) + '</span>' +
'</div>' +
'<div style="display:flex;justify-content:space-between;align-items:center;">' +
'<span style="color: var(--text-muted, #888);">Mantis Manager</span>' +
'<span style="display:flex;align-items:center;gap:4px;"><span class="as-status-dot green"></span> Mantis #1</span>' +
'</div>' +
'</div>' +
'<div style="padding:8px 16px;font-size:10px;font-weight:700;color: var(--text-muted, #888);">' +
'<div data-toggle="' + nodeId + '-files" style="padding:4px 0;cursor:pointer;user-select:none;" onclick="(function(el){var t=document.getElementById(el.getAttribute(\'data-toggle\'));if(t){t.style.display=t.style.display===\'none\'?\'\':\'none\';var a=el.querySelector(\'span\');if(a)a.textContent=t.style.display===\'none\'?\'▶\':\'▼\';}})(this)">// SUB-TASKS <span style="float:right;"></span></div>' +
'<div style="padding:4px 0;cursor:pointer;">// LOGS <span style="float:right;"></span></div>' +
'</div>' +
subTasksHtml;
if (isFirst || stepsContainer.children.length > 0) {
var line = document.createElement("div");
line.style.cssText = "position:absolute;right:-60px;top:50%;width:60px;height:2px;background:#84d669;z-index:10;";
node.appendChild(line);
if (!isFirst) {
var dot = document.createElement("div");
dot.style.cssText = "position:absolute;left:-5px;top:50%;transform:translateY(-50%);width:10px;height:10px;border-radius:50%;background:#84d669;z-index:20;";
node.appendChild(dot);
}
}
stepsContainer.appendChild(node);
stepsContainer.scrollLeft = stepsContainer.scrollWidth;
taskNodes.push({ title: title, description: description, meta: meta });
return node;
}
/* ── call /api/autotask/classify (real backend) ── */
function callAutotask(intent) {
updateMantis1("working");
vibeAddMsg("system", "🔄 Mantis #1 is analyzing your request…");
// Connect task progress WS to get live orchestrator events
connectTaskProgressWs(null);
// Update breadcrumb
var breadcrumb = document.querySelector(".vibe-canvas div:first-child");
if (breadcrumb) {
currentProject = intent.substring(0, 40).replace(/[^a-zA-Z0-9 ]/g, "");
breadcrumb.innerHTML = '// DASHBOARD <span style="color: var(--text-secondary, #ccc);margin:0 6px;">&gt;</span> // ' + esc(currentProject.toUpperCase()) + ' <div style="float:right;"><button style="border: 1px solid var(--border, #e0e0e0);background: var(--bg, #fff);border-radius:4px;padding:2px 8px;cursor:pointer;">-</button><span style="font-size:11px;margin:0 8px;color: var(--text, #333);">100%</span><button style="border: 1px solid var(--border, #e0e0e0);background: var(--bg, #fff);border-radius:4px;padding:2px 8px;cursor:pointer;">+</button></div>';
}
fetch("/api/autotask/classify", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ intent: intent, auto_process: true })
})
.then(function (r) { return r.json(); })
.then(function (data) {
updateMantis1("done");
if (data.success && data.result) {
var r = data.result;
// Connect to task-specific progress WS if we have a task_id
if (r.task_id) {
connectTaskProgressWs(r.task_id);
}
// Add task nodes from created_resources
if (r.created_resources && r.created_resources.length > 0) {
r.created_resources.forEach(function (res, i) {
setTimeout(function () {
addTaskNode(
res.name || res.resource_type,
res.resource_type + (res.path ? " → " + res.path : ""),
{ status: "Done" }
);
}, i * 400);
});
} else {
addTaskNode("Project Setup", "Setting up: " + intent, { status: "Planning" });
}
vibeAddMsg("bot", r.message || "Done! Your project is ready.");
if (r.app_url) {
vibeAddMsg("system", '✅ App available at <a href="' + r.app_url + '" target="_blank" style="color: var(--accent, #84d669);text-decoration:underline;">' + esc(r.app_url) + '</a>');
var preview = document.getElementById("vibePreview");
var urlBar = document.getElementById("vibePreviewUrl");
var content = document.getElementById("vibePreviewContent");
if (preview) preview.style.display = "";
if (urlBar) urlBar.value = r.app_url;
if (content) content.innerHTML = '<iframe src="' + r.app_url + '" style="width:100%;height:100%;border:none;"></iframe>';
}
if (r.next_steps && r.next_steps.length > 0) {
vibeAddMsg("bot", "**Next steps:**\n" + r.next_steps.map(function (s) { return "• " + s; }).join("\n"));
}
} else {
vibeAddMsg("bot", "I classified your intent as **" + (data.intent_type || "UNKNOWN") + "**. " + (data.error || "Processing complete."));
addTaskNode("Analysis", intent, { status: "Planning" });
}
})
.catch(function (err) {
updateMantis1("done");
vibeAddMsg("system", "⚠️ Backend unavailable — showing plan preview.");
var words = intent.split(/[.,;]/);
addTaskNode("Project Setup", "Create project structure and install dependencies", { status: "Planning" });
if (words.length > 1) {
setTimeout(function () {
addTaskNode("Database Schema", "Define tables for: " + words.slice(0, 3).join(", "), { status: "Pending" });
}, 500);
}
vibeAddMsg("bot", "I've created a preliminary plan with " + Math.min(words.length + 1, 5) + " nodes. Once the backend is available, I'll process the full build.");
});
}
/* ── WebSocket to backend (re-use chat WS) ── */
function connectVibeWs() {
setVibeStatus("connecting");
var botName = window.__INITIAL_BOT_NAME__ || "default";
fetch("/api/auth?bot_name=" + encodeURIComponent(botName))
.then(function (r) { return r.json(); })
.then(function (auth) {
vibeUserId = auth.user_id;
vibeSessionId = auth.session_id;
vibeBotId = auth.bot_id || "default";
vibeBotName = botName;
var proto = location.protocol === "https:" ? "wss://" : "ws://";
var url = proto + location.host + "/ws?session_id=" + vibeSessionId + "&user_id=" + vibeUserId + "&bot_name=" + vibeBotName;
vibeWs = new WebSocket(url);
vibeWs.onopen = function () {
setVibeStatus("connected");
};
vibeWs.onmessage = function (event) {
try {
var data = JSON.parse(event.data);
if (data.type === "connected") return;
if (data.event) return; // system events
// Agent-mode messages → update sidebar
if (data.type === "thought_process") {
vibeAddMsg("system", "💭 " + esc(data.content));
return;
}
if (data.type === "terminal_output") {
vibeAddMsg("system", "🖥️ " + esc(data.line));
return;
}
if (data.type === "step_progress") {
var pct = Math.round((data.current / data.total) * 100);
updateMantis1("working");
var bar = document.querySelector('.as-agent-card[data-agent-id="1"] .as-bar-fill');
if (bar) bar.style.width = pct + "%";
return;
}
// Bot responses → chat
if (data.message_type === 2) {
if (data.is_complete) {
if (vibeStreaming) {
vibeFinalizeStream();
} else if (data.content && data.content.trim()) {
vibeAddMsg("bot", data.content);
}
vibeStreaming = false;
} else {
if (!vibeStreaming) {
vibeStreaming = true;
vibeAddStreamStart();
vibeUpdateStream(data.content || "");
} else {
vibeUpdateStream(data.content || "");
}
}
}
} catch (e) {
console.error("Vibe WS parse error:", e);
}
};
vibeWs.onclose = function () { setVibeStatus("disconnected"); };
vibeWs.onerror = function () { setVibeStatus("disconnected"); };
})
.catch(function () {
setVibeStatus("disconnected");
vibeAddMsg("system", "⚠️ Could not connect to backend. You can still plan offline.");
});
}
/* ── send via WS (for regular chat) ── */
function vibeSendWs(content) {
if (vibeWs && vibeWs.readyState === WebSocket.OPEN) {
vibeWs.send(JSON.stringify({
bot_id: vibeBotId,
user_id: vibeUserId,
session_id: vibeSessionId,
channel: "web",
content: content,
message_type: 1,
timestamp: new Date().toISOString()
}));
}
}
/* ── Task Progress WebSocket (orchestrator events) ── */
var taskProgressWs = null;
function connectTaskProgressWs(taskId) {
var proto = location.protocol === "https:" ? "wss://" : "ws://";
var url = proto + location.host + "/ws/task-progress" + (taskId ? "/" + taskId : "");
if (taskProgressWs) {
try { taskProgressWs.close(); } catch (ignore) { }
}
taskProgressWs = new WebSocket(url);
taskProgressWs.onmessage = function (event) {
try {
var data = JSON.parse(event.data);
if (data.type === "connected") return;
if (data.event_type === "agent_thought" || data.step === "agent_thought") {
var agentLabel = (data.details || "mantis_1").replace("mantis_", "Mantis #");
vibeAddMsg("system", "💭 " + agentLabel + ": " + esc(data.text || data.message || ""));
return;
}
if (data.event_type === "agent_update" || data.step === "agent_update") {
try {
var info = typeof data.details === "string" ? JSON.parse(data.details) : data.details;
if (info) {
updateAgentCard(info.agent_id, info.status, info.detail);
}
} catch (ignore) { }
return;
}
if (data.event_type === "task_node" || data.step === "task_node") {
try {
var nodeInfo = typeof data.details === "string" ? JSON.parse(data.details) : data.details;
if (nodeInfo) {
addTaskNode(
nodeInfo.title || data.message || "Task",
nodeInfo.description || "",
{
status: nodeInfo.status || "Planning",
estimated_files: nodeInfo.estimated_files,
estimated_time: nodeInfo.estimated_time,
estimated_tokens: nodeInfo.estimated_tokens,
fileList: nodeInfo.files || []
}
);
}
} catch (ignore) {
addTaskNode(data.message || "Task", "", { status: "Planning" });
}
return;
}
if (data.event_type === "step_progress" || data.step === "step_progress") {
var pct = 0;
if (data.current_step && data.total_steps) {
pct = Math.round((data.current_step / data.total_steps) * 100);
} else if (data.current && data.total) {
pct = Math.round((data.current / data.total) * 100);
}
updateMantis1("working");
var bar = document.querySelector('.as-agent-card[data-agent-id="1"] .as-bar-fill');
if (bar) bar.style.width = pct + "%";
var stageMap = {
"Planning": "plan", "Building": "build",
"Reviewing": "review", "Deploying": "deploy",
"Monitoring": "monitor"
};
var stageLabel = data.message || "";
var tabStage = stageMap[stageLabel];
if (tabStage) {
var allTabs = document.querySelectorAll(".vibe-pipeline-tab");
allTabs.forEach(function (t) { t.classList.remove("active"); });
var activeTab = document.querySelector('.vibe-pipeline-tab[data-stage="' + tabStage + '"]');
if (activeTab) activeTab.classList.add("active");
}
return;
}
if (data.event_type === "pipeline_complete" || data.step === "pipeline_complete") {
updateMantis1("done");
vibeAddMsg("system", "✅ Pipeline complete — all stages finished");
return;
}
if (data.event_type === "manifest_update") {
return;
}
} catch (e) {
console.error("Task progress parse error:", e);
}
};
taskProgressWs.onerror = function () { };
taskProgressWs.onclose = function () { };
}
function updateAgentCard(agentId, status, detail) {
var card = document.querySelector('.as-agent-card[data-agent-id="' + agentId + '"]');
if (!card) return;
card.style.opacity = "1";
var badge = card.querySelector(".as-badge");
var dot = card.querySelector(".as-status-dot");
if (status === "WORKING") {
card.style.borderLeft = "3px solid #f59e0b";
if (dot) { dot.className = "as-status-dot yellow"; }
if (badge) { badge.textContent = "WORKING"; badge.className = "as-badge badge-bred"; }
if (!card.querySelector(".as-agent-bar")) {
var barWrapper = document.createElement("div");
barWrapper.className = "as-agent-bar";
barWrapper.innerHTML = '<div class="as-bar-fill bred" style="width:0%;transition:width 0.5s;"></div>';
card.appendChild(barWrapper);
}
} else if (status === "EVOLVED" || status === "DONE") {
card.style.borderLeft = "3px solid #84d669";
if (dot) { dot.className = "as-status-dot green"; }
if (badge) { badge.textContent = "EVOLVED"; badge.className = "as-badge badge-evolved"; }
var agBar = card.querySelector(".as-bar-fill");
if (agBar) agBar.style.width = "100%";
setTimeout(function () {
var b = card.querySelector(".as-agent-bar");
if (b) b.remove();
}, 2000);
} else if (status === "BRED") {
card.style.borderLeft = "3px solid #f59e0b";
if (dot) { dot.className = "as-status-dot yellow"; }
if (badge) { badge.textContent = "BRED"; badge.className = "as-badge badge-bred"; }
} else if (status === "FAILED") {
card.style.borderLeft = "3px solid #ef4444";
if (dot) { dot.className = "as-status-dot red"; }
if (badge) { badge.textContent = "FAILED"; badge.className = "as-badge badge-bred"; badge.style.background = "#ef4444"; }
}
if (detail) {
var detailEl = card.querySelector(".as-agent-detail");
if (!detailEl) {
detailEl = document.createElement("span");
detailEl.className = "as-agent-detail";
detailEl.style.cssText = "font-size:10px;color: var(--text-muted, #666);display:block;padding:0 12px 4px;";
var body = card.querySelector(".as-agent-body");
if (body) body.after(detailEl);
}
detailEl.textContent = detail;
}
}
/* ── form submit ── */
function handleVibeSubmit(e) {
e.preventDefault();
var input = document.getElementById("vibeChatInput");
if (!input) return;
var text = input.value.trim();
if (!text) return;
input.value = "";
vibeAddMsg("user", text);
// Decide: if the text looks like a "build" request → call autotask API
// Otherwise → send via WS for regular chat
var buildKeywords = /\b(create|build|make|develop|generate|design|scaffold|i want|i need|app for|website for|system for|platform for|dashboard for)\b/i;
if (buildKeywords.test(text)) {
callAutotask(text);
// Also send via WS so the bot knows
vibeSendWs(text);
} else {
vibeSendWs(text);
}
}
/* ── init ── */
2026-02-26 12:40:44 -03:00
function initVibe() {
setupPipelineTabs();
setupSidebarCollapse();
setupWorkspaceAccordions();
// Wire up chat form
var form = document.getElementById("vibeChatForm");
if (form) form.addEventListener("submit", handleVibeSubmit);
// Connect WebSocket
connectVibeWs();
}
2026-02-26 12:40:44 -03:00
function setupPipelineTabs() {
var container = document.querySelector(".vibe-pipeline");
if (!container) return;
container.addEventListener("click", function (e) {
var tab = e.target.closest(".vibe-pipeline-tab");
if (!tab) return;
container.querySelectorAll(".vibe-pipeline-tab").forEach(function (t) {
t.classList.remove("active");
});
2026-02-26 12:40:44 -03:00
tab.classList.add("active");
});
}
2026-02-26 12:40:44 -03:00
function setupSidebarCollapse() {
var btn = document.getElementById("agentsSidebarCollapse");
var sidebar = document.getElementById("agentsSidebar");
if (!btn || !sidebar) return;
btn.addEventListener("click", function () {
sidebar.classList.toggle("collapsed");
btn.textContent = sidebar.classList.contains("collapsed") ? "▶" : "◀";
});
}
2026-02-26 12:40:44 -03:00
function setupWorkspaceAccordions() {
var toggles = document.querySelectorAll(".as-workspace-toggle");
toggles.forEach(function (toggle) {
toggle.addEventListener("click", function () {
var body = this.nextElementSibling;
var arrow = this.querySelector(".as-workspace-arrow");
if (body) {
var isOpen = body.style.display !== "none";
body.style.display = isOpen ? "none" : "";
if (arrow) arrow.textContent = isOpen ? "▶" : "▼";
}
});
2026-02-26 12:40:44 -03:00
});
}
2026-02-26 12:40:44 -03:00
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initVibe);
} else {
initVibe();
}
})();
</script>
<style>
@keyframes nodeIn {
from {
opacity: 0;
transform: translateY(20px) scale(0.95);
}
to {
opacity: 1;
transform: translateY(0) scale(1);
}
}
@keyframes float {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-10px);
}
}
.vibe-quick-btn:hover {
border-color: var(--accent, #84d669) !important;
color: var(--accent, #84d669) !important;
background: rgba(132, 214, 105, 0.06) !important;
transform: translateY(-2px);
}
</style>