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 >
2026-03-01 22:36:15 -03:00
< button class = "vibe-pipeline-tab active" data-stage = "build" >
// BUILD
< / button >
2026-02-26 12:40:44 -03:00
< button class = "vibe-pipeline-tab" data-stage = "review" > // REVIEW< / button >
< button class = "vibe-pipeline-tab" data-stage = "deploy" > // DEPLOY< / button >
2026-03-01 22:36:15 -03:00
< button class = "vibe-pipeline-tab" data-stage = "monitor" >
// MONITOR
< / button >
2026-02-24 19:02:48 -03:00
< / div >
2026-02-26 12:40:44 -03:00
<!-- Main Content: Canvas + Sidebar -->
< div class = "vibe-body" >
2026-02-28 10:05:36 -03:00
<!-- Agents & Workspaces Sidebar (Fixed to left as in image) -->
2026-03-01 22:36:15 -03:00
< aside
class="agents-sidebar"
id="agentsSidebar"
style="background: var(--surface)"
>
< div
class="as-logo-section"
style="
padding: 16px;
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
"
>
2026-02-28 10:05:36 -03:00
< h2
2026-03-01 22:36:15 -03:00
style="
margin: 0;
font-size: 15px;
color: var(--text);
font-weight: 800;
display: flex;
align-items: center;
gap: 8px;
"
>
< span style = "font-size: 18px; color: var(--accent)"
>🌱< /span
>
mantis farm
2026-02-28 10:05:36 -03:00
< / h2 >
2026-03-01 22:36:15 -03:00
< div
style="
display: flex;
gap: 12px;
color: var(--text-muted);
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 >
2026-02-24 19:02:48 -03:00
< / div >
2026-02-26 12:40:44 -03:00
< div class = "as-section" >
< div class = "as-section-header" >
< h3 > Agents< / h3 >
2026-03-01 22:36:15 -03:00
< button
class="as-collapse-btn"
id="agentsSidebarCollapse"
type="button"
>
◀
< / button >
2026-02-26 12:40:44 -03:00
< / div >
< div class = "as-agent-list" id = "asAgentList" >
2026-02-28 10:05:36 -03:00
<!-- Mantis #1 EVOLVED -->
2026-03-01 22:36:15 -03:00
< div
class="as-agent-card"
data-agent-id="1"
style="border-left: 3px solid var(--accent)"
>
2026-02-28 10:05:36 -03:00
< div class = "as-agent-header" >
< span class = "as-status-dot green" > < / span >
< span class = "as-agent-name" > Mantis #1< / span >
2026-03-01 22:36:15 -03:00
< span
class="as-drag-handle"
style="margin-left: auto"
>⋮< /span
>
2026-02-28 10:05:36 -03:00
< / 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 >
2026-03-01 22:36:15 -03:00
< span
class="as-drag-handle"
style="margin-left: auto"
>⋮< /span
>
2026-02-28 10:05:36 -03:00
< / 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 -->
2026-03-01 22:36:15 -03:00
< div
class="as-agent-card"
style="opacity: 0.6"
data-agent-id="3"
>
2026-02-28 10:05:36 -03:00
< div class = "as-agent-header" >
< span class = "as-status-dot gray" > < / span >
< span class = "as-agent-name" > Mantis #3< / span >
2026-03-01 22:36:15 -03:00
< span
class="as-drag-handle"
style="margin-left: auto"
>⋮< /span
>
2026-02-28 10:05:36 -03:00
< / div >
< div class = "as-agent-body" >
2026-03-01 22:36:15 -03:00
< span
class="as-agent-icons"
style="filter: grayscale(1)"
>🥚< /span
>
< span
class="as-badge badge-wild"
style="background: var(--surface-active, #ccc)"
>WILD< /span
>
2026-02-28 10:05:36 -03:00
< / div >
< / div >
2026-03-01 22:36:15 -03:00
< div
class="as-agent-card"
style="opacity: 0.6"
data-agent-id="4"
>
2026-02-28 10:05:36 -03:00
< div class = "as-agent-header" >
< span class = "as-status-dot gray" > < / span >
< span class = "as-agent-name" > Mantis #4< / span >
2026-03-01 22:36:15 -03:00
< span
class="as-drag-handle"
style="margin-left: auto"
>⋮< /span
>
2026-02-28 10:05:36 -03:00
< / div >
< div class = "as-agent-body" >
2026-03-01 22:36:15 -03:00
< span
class="as-agent-icons"
style="filter: grayscale(1)"
>🥚< /span
>
< span
class="as-badge badge-wild"
style="background: var(--surface-active, #ccc)"
>WILD< /span
>
2026-02-28 10:05:36 -03:00
< / div >
2026-02-26 12:40:44 -03:00
< / div >
< / div >
2026-03-01 22:36:15 -03:00
< 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 >
2026-02-28 10:05:36 -03:00
< / 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" >
2026-03-01 22:36:15 -03:00
< button
class="as-workspace-toggle"
type="button"
style="
background: var(--bg);
border-left: 3px solid var(--accent);
"
>
2026-02-28 10:05:36 -03:00
< span class = "as-workspace-arrow" > ▼< / span >
< span > E-Commerce App Development< / span >
2026-02-26 12:40:44 -03:00
< / button >
2026-03-01 22:36:15 -03:00
< div class = "as-workspace-body" style = "display: block" >
2026-02-28 10:05:36 -03:00
< div class = "as-workspace-agent" > Mantis #1< / div >
< div class = "as-workspace-agent" > Mantis #4< / div >
2026-03-01 22:36:15 -03:00
< div
class="as-workspace-dropzone"
data-workspace="ecommerce"
>
2026-02-28 10:05:36 -03:00
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 >
2026-02-28 10:05:36 -03:00
< span > Accountability App Development< / span >
2026-02-26 12:40:44 -03:00
< / button >
2026-03-01 22:36:15 -03:00
< div class = "as-workspace-body" style = "display: none" >
2026-02-28 10:05:36 -03:00
< div class = "as-workspace-agent" > Mantis #4< / div >
2026-03-01 22:36:15 -03:00
< div
class="as-workspace-dropzone"
data-workspace="accountability"
>
2026-02-28 10:05:36 -03:00
Drag a Mantis to Include
2026-02-26 12:40:44 -03:00
< / div >
< / div >
< / div >
< / div >
2026-02-28 10:05:36 -03:00
2026-03-01 22:36:15 -03:00
< 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 >
2026-02-28 10:05:36 -03:00
< / div >
2026-02-26 12:40:44 -03:00
< / div >
< / aside >
2026-02-28 10:05:36 -03:00
<!-- Canvas Area -->
2026-03-01 22:36:15 -03:00
< div
class="vibe-canvas"
id="vibeCanvas"
style="
background: var(--bg, #fdfdfd);
background-image: radial-gradient(
var(--border) 1px,
transparent 1px
);
background-size: 20px 20px;
position: relative;
"
>
2026-02-28 10:05:36 -03:00
< div
2026-03-01 22:36:15 -03:00
style="
padding: 16px 24px;
font-size: 11px;
color: var(--text-muted);
font-weight: 600;
letter-spacing: 0.5px;
border-bottom: 1px solid var(--border);
background: rgba(255, 255, 255, 0.8);
backdrop-filter: blur(4px);
"
>
// DASHBOARD
< span style = "color: var(--text-secondary); margin: 0 6px"
>> < /span
>
// E-COMMERCE APP DEVELOPMENT
< div style = "float: right" >
2026-02-28 10:05:36 -03:00
< button
2026-03-01 22:36:15 -03:00
style="
border: 1px solid var(--border);
background: var(--bg);
border-radius: 4px;
padding: 2px 8px;
cursor: pointer;
"
>
-
< / button >
< span
style="
font-size: 11px;
margin: 0 8px;
color: var(--text);
"
>100%< /span
>
2026-02-28 10:05:36 -03:00
< button
2026-03-01 22:36:15 -03:00
style="
border: 1px solid var(--border);
background: var(--bg);
border-radius: 4px;
padding: 2px 8px;
cursor: pointer;
"
>
+
< / button >
2026-02-28 10:05:36 -03:00
< / div >
< / div >
<!-- Steps Nodes (populated dynamically from chat/API) -->
2026-03-01 22:36:15 -03:00
< div
class="vibe-steps"
id="vibeSteps"
style="
padding: 40px;
display: none;
gap: 60px;
align-items: flex-start;
overflow-x: auto;
"
>< / div >
2026-02-28 10:05:36 -03:00
<!-- Empty state -->
2026-03-01 22:36:15 -03:00
< 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 >
2026-02-28 10:05:36 -03:00
< h3
2026-03-01 22:36:15 -03:00
style="
margin: 0;
font-size: 22px;
font-weight: 800;
color: var(--text);
font-family: " Fira Code" , monospace;
"
>
Vibe — App Builder
< / h3 >
< p
style="
margin: 0;
font-size: 14px;
color: var(--text-muted);
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.
2026-02-28 10:05:36 -03:00
< / p >
2026-03-01 22:36:15 -03:00
< div
style="
display: flex;
gap: 10px;
flex-wrap: wrap;
justify-content: center;
margin-top: 12px;
"
>
< button
class="vibe-quick-btn"
type="button"
2026-02-28 10:05:36 -03:00
onclick="document.getElementById('vibeChatInput').value='Create an e-commerce app for selling handmade crafts with shopping cart and payments'; document.getElementById('vibeChatInput').focus();"
2026-03-01 22:36:15 -03:00
style="
padding: 8px 16px;
border: 1px solid var(--border);
border-radius: 20px;
background: var(--bg);
font-size: 12px;
cursor: pointer;
color: var(--text-muted);
transition: all 0.15s;
font-family: " Fira Code" , monospace;
"
>
2026-02-28 10:05:36 -03:00
🛍️ E-Commerce App
< / button >
2026-03-01 22:36:15 -03:00
< button
class="vibe-quick-btn"
type="button"
2026-02-28 10:05:36 -03:00
onclick="document.getElementById('vibeChatInput').value='Build a CRM system with contacts, leads, and deal pipeline tracking'; document.getElementById('vibeChatInput').focus();"
2026-03-01 22:36:15 -03:00
style="
padding: 8px 16px;
border: 1px solid var(--border);
border-radius: 20px;
background: var(--bg);
font-size: 12px;
cursor: pointer;
color: var(--text-muted);
transition: all 0.15s;
font-family: " Fira Code" , monospace;
"
>
2026-02-28 10:05:36 -03:00
📇 CRM System
< / button >
2026-03-01 22:36:15 -03:00
< button
class="vibe-quick-btn"
type="button"
2026-02-28 10:05:36 -03:00
onclick="document.getElementById('vibeChatInput').value='Create a project management dashboard with tasks, Kanban board, and team assignments'; document.getElementById('vibeChatInput').focus();"
2026-03-01 22:36:15 -03:00
style="
padding: 8px 16px;
border: 1px solid var(--border);
border-radius: 20px;
background: var(--bg);
font-size: 12px;
cursor: pointer;
color: var(--text-muted);
transition: all 0.15s;
font-family: " Fira Code" , monospace;
"
>
2026-02-28 10:05:36 -03:00
📊 Project Manager
< / button >
< / div >
< / div >
<!-- Vibe Chat Overlay (LIVE) -->
2026-03-01 22:36:15 -03:00
< div
id="vibeChatOverlay"
style="
position: absolute;
bottom: 24px;
right: 24px;
width: 380px;
background: var(--surface);
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);
color: var(--bg);
z-index: 100;
"
>
2026-02-28 10:05:36 -03:00
< div
2026-03-01 22:36:15 -03:00
style="
padding: 12px 16px;
background: var(--surface-hover);
border-bottom: 1px solid var(--border);
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 var(--accent)"
>< / span >
< span style = "font-size: 12px; font-weight: 600"
>Mantis #1< /span
>
2026-02-28 10:05:36 -03:00
< / div >
2026-03-01 22:36:15 -03:00
< span
id="vibeChatStatusBadge"
style="
font-size: 10px;
color: var(--text-muted);
background: #333;
padding: 2px 6px;
border-radius: 4px;
"
>CONNECTING…< /span
>
2026-02-28 10:05:36 -03:00
< / div >
2026-03-01 22:36:15 -03:00
< 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;
"
>
2026-02-28 10:05:36 -03:00
<!-- Welcome hint -->
< div
2026-03-01 22:36:15 -03:00
style="
align-self: center;
background: rgba(132, 214, 105, 0.12);
color: var(--accent);
padding: 8px 14px;
border-radius: 8px;
font-size: 11px;
text-align: center;
"
>
💡 TIP: Describe your project. The more detail, the
better the plan.
2026-02-28 10:05:36 -03:00
< / div >
< / div >
2026-03-01 22:36:15 -03:00
< div
style="
padding: 12px;
border-top: 1px solid var(--border);
background: var(--surface);
"
>
< form
id="vibeChatForm"
autocomplete="off"
style="
display: flex;
align-items: center;
gap: 8px;
background: var(--surface-hover);
padding: 8px 12px;
border-radius: 20px;
border: 1px solid #333;
"
>
< span
style="
color: var(--text-muted);
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);
font-size: 13px;
outline: none;
font-family:
" Segoe UI" , system-ui, sans-serif;
"
/>
< button
type="submit"
id="vibeChatSend"
style="
background: var(--accent);
color: var(--surface);
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 >
2026-02-28 10:05:36 -03:00
< / form >
< / div >
< / div >
<!-- Preview Panel (hidden by default) -->
2026-03-01 22:36:15 -03:00
< div class = "vibe-preview" id = "vibePreview" style = "display: none" >
2026-02-28 10:05:36 -03:00
< div class = "vibe-preview-header" >
< span > // PREVIEW< / span >
2026-03-01 22:36:15 -03:00
< input
type="text"
class="vibe-preview-url"
id="vibePreviewUrl"
value=""
readonly
/>
2026-02-28 10:05:36 -03:00
< / div >
< div class = "vibe-preview-content" id = "vibePreviewContent" > < / div >
< / div >
2026-03-02 07:12:30 -03:00
<!-- Deployment Panel (hidden by default) -->
< div
class="vibe-deployment-modal"
id="vibeDeploymentModal"
style="display: none"
>
< div
class="vibe-deployment-overlay"
onclick="closeDeploymentModal()"
>< / div >
< div
class="vibe-deployment-panel"
style="
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: var(--surface);
border-radius: 16px;
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.3);
width: 600px;
max-width: 90%;
max-height: 80vh;
overflow-y: auto;
border: 1px solid var(--border);
z-index: 1000;
"
>
< div
style="
padding: 24px;
border-bottom: 1px solid var(--border);
"
>
< h2
style="
margin: 0;
font-size: 20px;
color: var(--text);
display: flex;
align-items: center;
gap: 12px;
"
>
< span style = "font-size: 24px" > 🚀< / span >
Deploy Your App
< / h2 >
< p
style="
margin: 8px 0 0;
font-size: 14px;
color: var(--text-muted);
"
>
Choose where to deploy your application
< / p >
< / div >
< div style = "padding: 24px" >
<!-- Deployment Options -->
< div
style="
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
margin-bottom: 24px;
"
>
<!-- Internal Deployment -->
< div
class="deployment-option"
id="deploymentInternal"
onclick="selectDeploymentTarget('internal')"
style="
padding: 20px;
border: 2px solid var(--border);
border-radius: 12px;
cursor: pointer;
transition: all 0.2s;
"
>
< div
style="
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
"
>
< span style = "font-size: 32px" > 📱< / span >
< div >
< h3
style="
margin: 0;
font-size: 16px;
color: var(--text);
"
>
GB Platform
< / h3 >
< span
style="
font-size: 12px;
color: var(--text-muted);
"
>Internal< /span
>
< / div >
< / div >
< ul
style="
margin: 0;
padding-left: 20px;
font-size: 13px;
color: var(--text-muted);
line-height: 1.6;
"
>
< li > Serve from /apps/{name}< / li >
< li > Use GB API endpoints< / li >
< li > Fast iteration cycles< / li >
< li > Shared platform resources< / li >
< / ul >
< / div >
<!-- External Deployment -->
< div
class="deployment-option"
id="deploymentExternal"
onclick="selectDeploymentTarget('external')"
style="
padding: 20px;
border: 2px solid var(--border);
border-radius: 12px;
cursor: pointer;
transition: all 0.2s;
"
>
< div
style="
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
"
>
< span style = "font-size: 32px" > 🌐< / span >
< div >
< h3
style="
margin: 0;
font-size: 16px;
color: var(--text);
"
>
Forgejo ALM
< / h3 >
< span
style="
font-size: 12px;
color: var(--text-muted);
"
>External< /span
>
< / div >
< / div >
< ul
style="
margin: 0;
padding-left: 20px;
font-size: 13px;
color: var(--text-muted);
line-height: 1.6;
"
>
< li > Push to Git repository< / li >
< li > CI/CD pipeline automation< / li >
< li > Custom domain support< / li >
< li > Independent deployment< / li >
< / ul >
< / div >
< / div >
<!-- Internal Configuration (shown by default) -->
< div
id="deploymentInternalConfig"
style="margin-bottom: 24px"
>
< h3
style="
margin: 0 0 16px;
font-size: 14px;
color: var(--text);
"
>
Internal Deployment Settings
< / h3 >
< form id = "internalDeployForm" >
< div style = "margin-bottom: 16px" >
< label
style="
display: block;
margin-bottom: 8px;
font-size: 13px;
color: var(--text-muted);
"
>App Route< /label
>
< input
type="text"
id="deployRoute"
placeholder="my-app"
style="
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg);
color: var(--text);
font-size: 14px;
"
/>
< span
style="
font-size: 12px;
color: var(--text-muted);
margin-top: 4px;
display: block;
"
>
App will be available at: /apps/< span
id="deployRoutePreview"
>my-app< /span
>/
< / span >
< / div >
< div style = "margin-bottom: 16px" >
< label
style="
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: var(--text);
cursor: pointer;
"
>
< input
type="checkbox"
id="deploySharedResources"
checked
style="margin: 0"
/>
Use shared platform resources (database,
cache, storage)
< / label >
< / div >
< / form >
< / div >
<!-- External Configuration (hidden by default) -->
< div
id="deploymentExternalConfig"
style="display: none; margin-bottom: 24px"
>
< h3
style="
margin: 0 0 16px;
font-size: 14px;
color: var(--text);
"
>
Forgejo Repository Settings
< / h3 >
< form id = "externalDeployForm" >
< div style = "margin-bottom: 16px" >
< label
style="
display: block;
margin-bottom: 8px;
font-size: 13px;
color: var(--text-muted);
"
>Repository Name< /label
>
< input
type="text"
id="deployRepoName"
placeholder="my-app"
style="
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg);
color: var(--text);
font-size: 14px;
"
/>
< / div >
< div style = "margin-bottom: 16px" >
< label
style="
display: block;
margin-bottom: 8px;
font-size: 13px;
color: var(--text-muted);
"
>Custom Domain (optional)< /label
>
< input
type="text"
id="deployCustomDomain"
placeholder="myapp.example.com"
style="
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg);
color: var(--text);
font-size: 14px;
"
/>
< / div >
< div style = "margin-bottom: 16px" >
< label
style="
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: var(--text);
cursor: pointer;
"
>
< input
type="checkbox"
id="deployCiCd"
checked
style="margin: 0"
/>
Enable CI/CD pipeline (automatic builds
and deployments)
< / label >
< / div >
< div style = "margin-bottom: 16px" >
< label
style="
display: block;
margin-bottom: 8px;
font-size: 13px;
color: var(--text-muted);
"
>App Type< /label
>
< select
id="deployAppType"
style="
width: 100%;
padding: 10px 12px;
border: 1px solid var(--border);
border-radius: 8px;
background: var(--bg);
color: var(--text);
font-size: 14px;
"
>
< option value = "htmx" >
HTMX App (Server-side rendering)
< / option >
< option value = "react" >
React App (SPA)
< / option >
< option value = "vue" >
Vue App (SPA)
< / option >
< / select >
< / div >
< / form >
< / div >
<!-- Action Buttons -->
< div
style="
display: flex;
gap: 12px;
justify-content: flex-end;
"
>
< button
type="button"
onclick="closeDeploymentModal()"
style="
padding: 10px 20px;
border: 1px solid var(--border);
border-radius: 8px;
background: transparent;
color: var(--text);
font-size: 14px;
cursor: pointer;
"
>
Cancel
< / button >
< button
type="button"
id="deployButton"
onclick="executeDeployment()"
style="
padding: 10px 20px;
border: none;
border-radius: 8px;
background: var(--accent);
color: white;
font-size: 14px;
font-weight: 600;
cursor: pointer;
"
>
Deploy Now
< / button >
< / div >
< / div >
< / div >
< / div >
2026-02-28 10:05:36 -03:00
< / div >
2026-02-24 19:02:48 -03:00
< / div >
< / div >
< script >
(function () {
"use strict";
2026-02-28 10:05:36 -03:00
/* ── 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") {
2026-03-01 22:36:15 -03:00
div.style.cssText =
"align-self:flex-end;background:var(--accent);color:var(--surface);font-weight:500;padding:10px 14px;border-radius:12px 12px 0 12px;max-width:85%;word-wrap:break-word;";
2026-02-28 10:05:36 -03:00
div.textContent = text;
} else if (role === "system") {
2026-03-01 22:36:15 -03:00
div.style.cssText =
"align-self:center;background:rgba(132,214,105,0.12);color: var(--accent);padding:6px 12px;border-radius:8px;font-size:11px;text-align:center;";
2026-02-28 10:05:36 -03:00
div.innerHTML = text;
} else {
2026-03-01 22:36:15 -03:00
div.style.cssText =
"align-self:flex-start;background: var(--border);color:#ececec;padding:10px 14px;border-radius:12px 12px 12px 0;max-width:85%;word-wrap:break-word;";
2026-02-28 10:05:36 -03:00
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) {
2026-03-01 22:36:15 -03:00
vibeStreamContent += content || "";
2026-02-28 10:05:36 -03:00
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") {
2026-03-01 22:36:15 -03:00
if (dot) {
dot.className = "as-status-dot green";
dot.style.boxShadow = "0 0 8px var(--accent)";
}
if (badge) {
badge.textContent = "EVOLVED";
badge.style.background = "var(--accent)";
badge.style.color = "var(--bg)";
}
2026-02-28 10:05:36 -03:00
} else if (status === "connecting") {
2026-03-01 22:36:15 -03:00
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 = "var(--text-muted)";
}
2026-02-28 10:05:36 -03:00
} else {
2026-03-01 22:36:15 -03:00
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 = "var(--text-muted)";
}
2026-02-28 10:05:36 -03:00
}
}
/* ── update agent sidebar card ── */
function updateMantis1(status, detail) {
2026-03-01 22:36:15 -03:00
var card = document.querySelector(
'.as-agent-card[data-agent-id="1"]',
);
2026-02-28 10:05:36 -03:00
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";
2026-03-01 22:36:15 -03:00
barWrapper.innerHTML =
'< div class = "as-bar-fill bred" style = "width:0%;transition:width 0.5s;" > < / div > ';
2026-02-28 10:05:36 -03:00
card.appendChild(barWrapper);
}
} else if (status === "done") {
2026-02-28 11:20:40 -03:00
card.style.borderLeftColor = "var(--accent)";
2026-02-28 10:05:36 -03:00
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 || {};
2026-03-01 22:36:15 -03:00
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";
2026-02-28 10:05:36 -03:00
var status = meta.status || "Planning";
var fileList = meta.fileList || [];
var isFirst = stepsContainer.children.length === 0;
var nodeId = "vibe-node-" + nodeIdCounter;
2026-03-01 22:36:15 -03:00
var statusBg =
status === "Done"
? "var(--accent)"
: status === "Planning"
? "var(--success-light, #eef8eb)"
: "var(--warning-light, var(--bg)3cd)";
var statusColor =
status === "Done"
? "var(--bg)"
: status === "Planning"
? "var(--accent)"
: "var(--warning, #856404)";
2026-02-28 10:05:36 -03:00
var subTasksHtml = "";
if (fileList.length > 0) {
2026-03-01 22:36:15 -03:00
subTasksHtml =
'< div id = "' +
nodeId +
'-files" style="display:none;padding:8px 16px;border-top:1px solid var(--border);font-size:10px;color:var(--text-muted, #555);">';
2026-02-28 10:05:36 -03:00
for (var fi = 0; fi < fileList.length ; fi + + ) {
2026-03-01 22:36:15 -03:00
subTasksHtml +=
'< div style = "padding:2px 0;display:flex;align-items:center;gap:4px;" > < span style = "color: var(--accent);" > 📄< / span > ' +
esc(fileList[fi]) +
"< / div > ";
2026-02-28 10:05:36 -03:00
}
2026-03-01 22:36:15 -03:00
subTasksHtml += "< / div > ";
2026-02-28 10:05:36 -03:00
}
var node = document.createElement("div");
node.className = "vibe-task-node";
2026-03-01 22:36:15 -03:00
node.style.cssText =
"background: var(--bg);border:" +
(isFirst
? "2px solid var(--accent)"
: "1px solid var(--border)") +
";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;";
2026-02-28 10:05:36 -03:00
node.innerHTML =
2026-02-28 11:08:54 -03:00
'< div style = "padding:12px 16px;border-bottom: 1px solid var(--border);" > ' +
2026-02-28 11:20:40 -03:00
'< div style = "display:flex;justify-content:space-between;margin-bottom:8px;font-size:10px;color: var(--text-muted);" > ' +
2026-03-01 22:36:15 -03:00
"< span > " +
fileCount +
" files< / span > < span > " +
time +
"< / span > < span > " +
tokens +
"< / span > " +
"< / div > " +
'< h4 style = "margin:0 0 8px 0;font-size:14px;color: var(--text);font-weight:700;" > ' +
esc(title) +
"< / h4 > " +
'< p style = "margin:0;font-size:11px;color: var(--text-muted);line-height:1.4;" > ' +
esc(description) +
"< / p > " +
"< / div > " +
2026-02-28 11:08:54 -03:00
'< div style = "padding:10px 16px;background: var(--surface);border-bottom: 1px solid var(--border);font-size:11px;" > ' +
2026-02-28 10:05:36 -03:00
'< div style = "display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;" > ' +
2026-02-28 11:20:40 -03:00
'< span style = "color: var(--text-muted);" > Status< / span > ' +
2026-03-01 22:36:15 -03:00
'< span style = "background:' +
statusBg +
";color:" +
statusColor +
';padding:2px 8px;border-radius:12px;font-weight:600;">' +
esc(status) +
"< / span > " +
"< / div > " +
2026-02-28 10:05:36 -03:00
'< div style = "display:flex;justify-content:space-between;align-items:center;" > ' +
2026-02-28 11:20:40 -03:00
'< span style = "color: var(--text-muted);" > Mantis Manager< / span > ' +
2026-02-28 10:05:36 -03:00
'< span style = "display:flex;align-items:center;gap:4px;" > < span class = "as-status-dot green" > < / span > Mantis #1< / span > ' +
2026-03-01 22:36:15 -03:00
"< / div > " +
"< / div > " +
2026-02-28 11:20:40 -03:00
'< div style = "padding:8px 16px;font-size:10px;font-weight:700;color: var(--text-muted);" > ' +
2026-03-01 22:36:15 -03:00
'< 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 > " +
2026-02-28 10:05:36 -03:00
'< div style = "padding:4px 0;cursor:pointer;" > // LOGS < span style = "float:right;" > ▶< / span > < / div > ' +
2026-03-01 22:36:15 -03:00
"< / div > " +
2026-02-28 10:05:36 -03:00
subTasksHtml;
if (isFirst || stepsContainer.children.length > 0) {
var line = document.createElement("div");
2026-03-01 22:36:15 -03:00
line.style.cssText =
"position:absolute;right:-60px;top:50%;width:60px;height:2px;background:var(--accent);z-index:10;";
2026-02-28 10:05:36 -03:00
node.appendChild(line);
if (!isFirst) {
var dot = document.createElement("div");
2026-03-01 22:36:15 -03:00
dot.style.cssText =
"position:absolute;left:-5px;top:50%;transform:translateY(-50%);width:10px;height:10px;border-radius:50%;background:var(--accent);z-index:20;";
2026-02-28 10:05:36 -03:00
node.appendChild(dot);
}
}
stepsContainer.appendChild(node);
stepsContainer.scrollLeft = stepsContainer.scrollWidth;
2026-03-01 22:36:15 -03:00
taskNodes.push({
title: title,
description: description,
meta: meta,
});
2026-02-28 10:05:36 -03:00
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
2026-03-01 22:36:15 -03:00
var breadcrumb = document.querySelector(
".vibe-canvas div:first-child",
);
2026-02-28 10:05:36 -03:00
if (breadcrumb) {
2026-03-01 22:36:15 -03:00
currentProject = intent
.substring(0, 40)
.replace(/[^a-zA-Z0-9 ]/g, "");
breadcrumb.innerHTML =
'// DASHBOARD < span style = "color: var(--text-secondary);margin:0 6px;" > > < / span > // ' +
esc(currentProject.toUpperCase()) +
' < div style = "float:right;" > < button style = "border: 1px solid var(--border);background: var(--bg);border-radius:4px;padding:2px 8px;cursor:pointer;" > -< / button > < span style = "font-size:11px;margin:0 8px;color: var(--text);" > 100%< / span > < button style = "border: 1px solid var(--border);background: var(--bg);border-radius:4px;padding:2px 8px;cursor:pointer;" > +< / button > < / div > ';
2026-02-28 10:05:36 -03:00
}
2026-03-01 22:36:15 -03:00
var token =
localStorage.getItem("gb-access-token") ||
sessionStorage.getItem("gb-access-token");
2026-02-28 10:05:36 -03:00
fetch("/api/autotask/classify", {
method: "POST",
2026-03-01 22:36:15 -03:00
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
},
body: JSON.stringify({ intent: intent, auto_process: true }),
2026-02-28 10:05:36 -03:00
})
2026-03-01 22:36:15 -03:00
.then(function (r) {
return r.json();
})
2026-02-28 10:05:36 -03:00
.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
2026-03-01 22:36:15 -03:00
if (
r.created_resources & &
r.created_resources.length > 0
) {
2026-02-28 10:05:36 -03:00
r.created_resources.forEach(function (res, i) {
setTimeout(function () {
addTaskNode(
res.name || res.resource_type,
2026-03-01 22:36:15 -03:00
res.resource_type +
(res.path ? " → " + res.path : ""),
{ status: "Done" },
2026-02-28 10:05:36 -03:00
);
}, i * 400);
});
} else {
2026-03-01 22:36:15 -03:00
addTaskNode(
"Project Setup",
"Setting up: " + intent,
{ status: "Planning" },
);
2026-02-28 10:05:36 -03:00
}
2026-03-01 22:36:15 -03:00
vibeAddMsg(
"bot",
r.message || "Done! Your project is ready.",
);
2026-02-28 10:05:36 -03:00
if (r.app_url) {
2026-03-01 22:36:15 -03:00
vibeAddMsg(
"system",
'✅ App available at < a href = "' +
r.app_url +
'" target="_blank" style="color: var(--accent);text-decoration:underline;">' +
esc(r.app_url) +
"< / a > ",
);
var preview =
document.getElementById("vibePreview");
var urlBar =
document.getElementById("vibePreviewUrl");
var content =
document.getElementById("vibePreviewContent");
2026-02-28 10:05:36 -03:00
if (preview) preview.style.display = "";
if (urlBar) urlBar.value = r.app_url;
2026-03-01 22:36:15 -03:00
if (content)
content.innerHTML =
'< iframe src = "' +
r.app_url +
'" style="width:100%;height:100%;border:none;">< / iframe > ';
2026-02-28 10:05:36 -03:00
}
if (r.next_steps & & r.next_steps.length > 0) {
2026-03-01 22:36:15 -03:00
vibeAddMsg(
"bot",
"**Next steps:**\n" +
r.next_steps
.map(function (s) {
return "• " + s;
})
.join("\n"),
);
2026-02-28 10:05:36 -03:00
}
} else {
2026-03-01 22:36:15 -03:00
vibeAddMsg(
"bot",
"I classified your intent as **" +
(data.intent_type || "UNKNOWN") +
"**. " +
(data.error || "Processing complete."),
);
2026-02-28 10:05:36 -03:00
addTaskNode("Analysis", intent, { status: "Planning" });
}
})
.catch(function (err) {
updateMantis1("done");
2026-03-01 22:36:15 -03:00
vibeAddMsg(
"system",
"⚠️ Backend unavailable — showing plan preview.",
);
2026-02-28 10:05:36 -03:00
var words = intent.split(/[.,;]/);
2026-03-01 22:36:15 -03:00
addTaskNode(
"Project Setup",
"Create project structure and install dependencies",
{ status: "Planning" },
);
2026-02-28 10:05:36 -03:00
if (words.length > 1) {
setTimeout(function () {
2026-03-01 22:36:15 -03:00
addTaskNode(
"Database Schema",
"Define tables for: " +
words.slice(0, 3).join(", "),
{ status: "Pending" },
);
2026-02-28 10:05:36 -03:00
}, 500);
}
2026-03-01 22:36:15 -03:00
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.",
);
2026-02-28 10:05:36 -03:00
});
}
/* ── 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))
2026-03-01 22:36:15 -03:00
.then(function (r) {
return r.json();
})
2026-02-28 10:05:36 -03:00
.then(function (auth) {
vibeUserId = auth.user_id;
vibeSessionId = auth.session_id;
vibeBotId = auth.bot_id || "default";
vibeBotName = botName;
2026-03-01 22:36:15 -03:00
var proto =
location.protocol === "https:" ? "wss://" : "ws://";
var url =
proto +
location.host +
"/ws?session_id=" +
vibeSessionId +
"& user_id=" +
vibeUserId +
"& bot_name=" +
vibeBotName;
2026-02-28 10:05:36 -03:00
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") {
2026-03-01 22:36:15 -03:00
var pct = Math.round(
(data.current / data.total) * 100,
);
2026-02-28 10:05:36 -03:00
updateMantis1("working");
2026-03-01 22:36:15 -03:00
var bar = document.querySelector(
'.as-agent-card[data-agent-id="1"] .as-bar-fill',
);
2026-02-28 10:05:36 -03:00
if (bar) bar.style.width = pct + "%";
return;
}
// Bot responses → chat
if (data.message_type === 2) {
if (data.is_complete) {
if (vibeStreaming) {
vibeFinalizeStream();
2026-03-01 22:36:15 -03:00
} else if (
data.content & &
data.content.trim()
) {
2026-02-28 10:05:36 -03:00
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);
}
};
2026-03-01 22:36:15 -03:00
vibeWs.onclose = function () {
setVibeStatus("disconnected");
};
vibeWs.onerror = function () {
setVibeStatus("disconnected");
};
2026-02-28 10:05:36 -03:00
})
.catch(function () {
setVibeStatus("disconnected");
2026-03-01 22:36:15 -03:00
vibeAddMsg(
"system",
"⚠️ Could not connect to backend. You can still plan offline.",
);
2026-02-28 10:05:36 -03:00
});
}
/* ── send via WS (for regular chat) ── */
function vibeSendWs(content) {
if (vibeWs & & vibeWs.readyState === WebSocket.OPEN) {
2026-03-01 22:36:15 -03:00
vibeWs.send(
JSON.stringify({
bot_id: vibeBotId,
user_id: vibeUserId,
session_id: vibeSessionId,
channel: "web",
content: content,
message_type: 1,
timestamp: new Date().toISOString(),
}),
);
2026-02-28 10:05:36 -03:00
}
}
/* ── Task Progress WebSocket (orchestrator events) ── */
var taskProgressWs = null;
function connectTaskProgressWs(taskId) {
var proto = location.protocol === "https:" ? "wss://" : "ws://";
2026-03-01 22:36:15 -03:00
var url =
proto +
location.host +
"/ws/task-progress" +
(taskId ? "/" + taskId : "");
2026-02-28 10:05:36 -03:00
if (taskProgressWs) {
2026-03-01 22:36:15 -03:00
try {
taskProgressWs.close();
} catch (ignore) {}
2026-02-28 10:05:36 -03:00
}
taskProgressWs = new WebSocket(url);
taskProgressWs.onmessage = function (event) {
try {
var data = JSON.parse(event.data);
if (data.type === "connected") return;
2026-03-01 22:36:15 -03:00
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 || ""),
);
2026-02-28 10:05:36 -03:00
return;
}
2026-03-01 22:36:15 -03:00
if (
data.event_type === "agent_update" ||
data.step === "agent_update"
) {
2026-02-28 10:05:36 -03:00
try {
2026-03-01 22:36:15 -03:00
var info =
typeof data.details === "string"
? JSON.parse(data.details)
: data.details;
2026-02-28 10:05:36 -03:00
if (info) {
2026-03-01 22:36:15 -03:00
updateAgentCard(
info.agent_id,
info.status,
info.detail,
);
2026-02-28 10:05:36 -03:00
}
2026-03-01 22:36:15 -03:00
} catch (ignore) {}
2026-02-28 10:05:36 -03:00
return;
}
2026-03-01 22:36:15 -03:00
if (
data.event_type === "task_node" ||
data.step === "task_node"
) {
2026-02-28 10:05:36 -03:00
try {
2026-03-01 22:36:15 -03:00
var nodeInfo =
typeof data.details === "string"
? JSON.parse(data.details)
: data.details;
2026-02-28 10:05:36 -03:00
if (nodeInfo) {
addTaskNode(
nodeInfo.title || data.message || "Task",
nodeInfo.description || "",
{
status: nodeInfo.status || "Planning",
2026-03-01 22:36:15 -03:00
estimated_files:
nodeInfo.estimated_files,
2026-02-28 10:05:36 -03:00
estimated_time: nodeInfo.estimated_time,
2026-03-01 22:36:15 -03:00
estimated_tokens:
nodeInfo.estimated_tokens,
fileList: nodeInfo.files || [],
},
2026-02-28 10:05:36 -03:00
);
}
} catch (ignore) {
2026-03-01 22:36:15 -03:00
addTaskNode(data.message || "Task", "", {
status: "Planning",
});
2026-02-28 10:05:36 -03:00
}
return;
}
2026-03-01 22:36:15 -03:00
if (
data.event_type === "step_progress" ||
data.step === "step_progress"
) {
2026-02-28 10:05:36 -03:00
var pct = 0;
if (data.current_step & & data.total_steps) {
2026-03-01 22:36:15 -03:00
pct = Math.round(
(data.current_step / data.total_steps) * 100,
);
2026-02-28 10:05:36 -03:00
} else if (data.current & & data.total) {
pct = Math.round((data.current / data.total) * 100);
}
updateMantis1("working");
2026-03-01 22:36:15 -03:00
var bar = document.querySelector(
'.as-agent-card[data-agent-id="1"] .as-bar-fill',
);
2026-02-28 10:05:36 -03:00
if (bar) bar.style.width = pct + "%";
var stageMap = {
2026-03-01 22:36:15 -03:00
Planning: "plan",
Building: "build",
Reviewing: "review",
Deploying: "deploy",
Monitoring: "monitor",
2026-02-28 10:05:36 -03:00
};
var stageLabel = data.message || "";
var tabStage = stageMap[stageLabel];
if (tabStage) {
2026-03-01 22:36:15 -03:00
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 +
'"]',
);
2026-02-28 10:05:36 -03:00
if (activeTab) activeTab.classList.add("active");
}
return;
}
2026-03-01 22:36:15 -03:00
if (
data.event_type === "pipeline_complete" ||
data.step === "pipeline_complete"
) {
2026-02-28 10:05:36 -03:00
updateMantis1("done");
2026-03-01 22:36:15 -03:00
vibeAddMsg(
"system",
"✅ Pipeline complete — all stages finished",
);
2026-02-28 10:05:36 -03:00
return;
}
if (data.event_type === "manifest_update") {
return;
}
} catch (e) {
console.error("Task progress parse error:", e);
}
};
2026-03-01 22:36:15 -03:00
taskProgressWs.onerror = function () {};
taskProgressWs.onclose = function () {};
2026-02-28 10:05:36 -03:00
}
function updateAgentCard(agentId, status, detail) {
2026-03-01 22:36:15 -03:00
var card = document.querySelector(
'.as-agent-card[data-agent-id="' + agentId + '"]',
);
2026-02-28 10:05:36 -03:00
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";
2026-03-01 22:36:15 -03:00
if (dot) {
dot.className = "as-status-dot yellow";
}
if (badge) {
badge.textContent = "WORKING";
badge.className = "as-badge badge-bred";
}
2026-02-28 10:05:36 -03:00
if (!card.querySelector(".as-agent-bar")) {
var barWrapper = document.createElement("div");
barWrapper.className = "as-agent-bar";
2026-03-01 22:36:15 -03:00
barWrapper.innerHTML =
'< div class = "as-bar-fill bred" style = "width:0%;transition:width 0.5s;" > < / div > ';
2026-02-28 10:05:36 -03:00
card.appendChild(barWrapper);
}
} else if (status === "EVOLVED" || status === "DONE") {
2026-02-28 11:20:40 -03:00
card.style.borderLeft = "3px solid var(--accent)";
2026-03-01 22:36:15 -03:00
if (dot) {
dot.className = "as-status-dot green";
}
if (badge) {
badge.textContent = "EVOLVED";
badge.className = "as-badge badge-evolved";
}
2026-02-28 10:05:36 -03:00
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";
2026-03-01 22:36:15 -03:00
if (dot) {
dot.className = "as-status-dot yellow";
}
if (badge) {
badge.textContent = "BRED";
badge.className = "as-badge badge-bred";
}
2026-02-28 10:05:36 -03:00
} else if (status === "FAILED") {
card.style.borderLeft = "3px solid #ef4444";
2026-03-01 22:36:15 -03:00
if (dot) {
dot.className = "as-status-dot red";
}
if (badge) {
badge.textContent = "FAILED";
badge.className = "as-badge badge-bred";
badge.style.background = "#ef4444";
}
2026-02-28 10:05:36 -03:00
}
if (detail) {
var detailEl = card.querySelector(".as-agent-detail");
if (!detailEl) {
detailEl = document.createElement("span");
detailEl.className = "as-agent-detail";
2026-03-01 22:36:15 -03:00
detailEl.style.cssText =
"font-size:10px;color: var(--text-muted);display:block;padding:0 12px 4px;";
2026-02-28 10:05:36 -03:00
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
2026-03-01 22:36:15 -03:00
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;
2026-02-28 10:05:36 -03:00
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();
2026-02-28 10:05:36 -03:00
// Wire up chat form
var form = document.getElementById("vibeChatForm");
if (form) form.addEventListener("submit", handleVibeSubmit);
// Connect WebSocket
connectVibeWs();
2026-02-24 19:02:48 -03:00
}
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;
2026-03-01 22:36:15 -03:00
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-24 19:02:48 -03:00
});
}
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");
2026-03-01 22:36:15 -03:00
btn.textContent = sidebar.classList.contains("collapsed")
? "▶"
: "◀";
2026-02-24 19:02:48 -03:00
});
}
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-25 10:15:47 -03:00
}
2026-02-24 19:02:48 -03:00
});
2026-02-26 12:40:44 -03:00
});
2026-02-24 19:02:48 -03:00
}
2026-03-02 07:12:30 -03:00
/* ── Deployment Modal Functions ── */
var selectedDeploymentTarget = "internal";
function selectDeploymentTarget(target) {
selectedDeploymentTarget = target;
// Update visual selection
var internalOption = document.getElementById("deploymentInternal");
var externalOption = document.getElementById("deploymentExternal");
var internalConfig = document.getElementById(
"deploymentInternalConfig",
);
var externalConfig = document.getElementById(
"deploymentExternalConfig",
);
if (target === "internal") {
internalOption.style.borderColor = "var(--accent)";
internalOption.style.background = "rgba(132, 214, 105, 0.06)";
externalOption.style.borderColor = "var(--border)";
externalOption.style.background = "transparent";
internalConfig.style.display = "block";
externalConfig.style.display = "none";
} else {
externalOption.style.borderColor = "var(--accent)";
externalOption.style.background = "rgba(132, 214, 105, 0.06)";
internalOption.style.borderColor = "var(--border)";
internalOption.style.background = "transparent";
internalConfig.style.display = "none";
externalConfig.style.display = "block";
}
}
function showDeploymentModal() {
var modal = document.getElementById("vibeDeploymentModal");
if (modal) {
modal.style.display = "block";
selectDeploymentTarget("internal");
}
}
function closeDeploymentModal() {
var modal = document.getElementById("vibeDeploymentModal");
if (modal) {
modal.style.display = "none";
}
}
function executeDeployment() {
var deployButton = document.getElementById("deployButton");
if (deployButton) {
deployButton.textContent = "Deploying...";
deployButton.disabled = true;
}
if (selectedDeploymentTarget === "internal") {
var route =
document.getElementById("deployRoute").value || "my-app";
var sharedResources = document.getElementById(
"deploySharedResources",
).checked;
vibeAddMsg(
"system",
"🚀 Deploying to GB Platform at /apps/" + route + "/...",
);
// Simulate deployment (in real implementation, this would call the backend API)
setTimeout(function () {
closeDeploymentModal();
vibeAddMsg(
"system",
"✅ App deployed successfully to /apps/" + route + "/",
);
// Show preview
var previewUrl = document.getElementById("vibePreviewUrl");
var previewPanel = document.getElementById("vibePreview");
if (previewUrl & & previewPanel) {
previewUrl.value =
window.location.origin + "/apps/" + route + "/";
previewPanel.style.display = "block";
}
if (deployButton) {
deployButton.textContent = "Deploy Now";
deployButton.disabled = false;
}
}, 2000);
} else {
var repoName =
document.getElementById("deployRepoName").value || "my-app";
var customDomain =
document.getElementById("deployCustomDomain").value;
var ciCd = document.getElementById("deployCiCd").checked;
var appType = document.getElementById("deployAppType").value;
vibeAddMsg(
"system",
"🌐 Creating Forgejo repository: " + repoName,
);
// Simulate deployment (in real implementation, this would call the backend API)
setTimeout(function () {
closeDeploymentModal();
var message =
"✅ Repository created and deployed to Forgejo";
if (customDomain) {
message += " (custom domain: " + customDomain + ")";
}
vibeAddMsg("system", message);
if (deployButton) {
deployButton.textContent = "Deploy Now";
deployButton.disabled = false;
}
}, 3000);
}
}
// Update route preview in real-time
if (document.readyState !== "loading") {
var routeInput = document.getElementById("deployRoute");
if (routeInput) {
routeInput.addEventListener("input", function () {
var preview = document.getElementById("deployRoutePreview");
if (preview) {
preview.textContent = this.value || "my-app";
}
});
}
}
2026-02-26 12:40:44 -03:00
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initVibe);
} else {
initVibe();
}
2026-02-24 19:02:48 -03:00
})();
2026-02-28 10:05:36 -03:00
< / 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 {
2026-02-28 11:20:40 -03:00
border-color: var(--accent) !important;
color: var(--accent) !important;
2026-02-28 10:05:36 -03:00
background: rgba(132, 214, 105, 0.06) !important;
transform: translateY(-2px);
}
2026-03-01 22:36:15 -03:00
< / style >