Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 2 additions & 9 deletions apps/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,6 @@
<link rel="stylesheet" href="/design-system/components/tags/tags.css" />
</head>
<body>
<header class="header">
<div class="header-left">
<h1>Agent Builder</h1>
</div>
<button id="btn-help" class="button button-text-primary button-small header-help" type="button">Help</button>
</header>

<main class="main-layout">
<div class="content-area" id="canvas-container">
<button id="btn-clear" class="button button-danger canvas-clear" type="button">Clear Canvas</button>
Expand Down Expand Up @@ -73,7 +66,7 @@ <h3>Nodes</h3>
<div class="chat-panel">
<div class="chat-header">
<h2>Run Console</h2>
<div class="chat-status" id="chat-status">Idle</div>
<button id="btn-help" class="button button-text-primary button-small" type="button">Help</button>
</div>
<div id="chat-messages" class="chat-messages">
<div class="chat-message system">
Expand Down Expand Up @@ -102,7 +95,7 @@ <h2 id="confirm-modal-title">Confirm Action</h2>
<div class="modal-body">
<p id="confirm-modal-message">Are you sure you want to continue?</p>
</div>
<div class="modal-footer" style="padding: 1rem; display: flex; justify-content: flex-end; gap: 0.5rem;">
<div class="modal-footer">
<button id="confirm-modal-cancel" class="button button-secondary">Cancel</button>
<button id="confirm-modal-confirm" class="button button-primary">Confirm</button>
</div>
Expand Down
117 changes: 83 additions & 34 deletions apps/web/src/app/workflow-editor.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// @ts-nocheck
// Bespoke Agent Builder - Client Logic

import type { WorkflowGraph } from '@agentic/types';

Check warning on line 4 in apps/web/src/app/workflow-editor.ts

View workflow job for this annotation

GitHub Actions / build-and-test (20.x)

'WorkflowGraph' is defined but never used. Allowed unused vars must match /^_/u
import { runWorkflow, resumeWorkflow } from '../services/api';

const COLLAPSED_NODE_WIDTH = 240;
const EXPANDED_NODE_WIDTH = 420;
const DEFAULT_NODE_WIDTH = 150; // Fallback if DOM not ready
const MODEL_OPTIONS = ['gpt-5', 'gpt-5-mini', 'gpt-5.1'];
const MODEL_EFFORTS = {
'gpt-5': ['low', 'medium', 'high'],
Expand Down Expand Up @@ -38,8 +38,8 @@
this.connectionsLayer = document.getElementById('connections-layer');
this.chatMessages = document.getElementById('chat-messages');
this.initialPrompt = document.getElementById('initial-prompt');
this.chatStatusEl = document.getElementById('chat-status');
this.runButton = document.getElementById('btn-run');
this.workflowState = 'idle'; // 'idle' | 'running' | 'paused'
this.rightPanel = document.getElementById('right-panel');
this.rightResizer = document.getElementById('right-resizer');
this.pendingAgentMessage = null;
Expand All @@ -62,8 +62,7 @@
this.initWebSocket();

this.applyViewport();
this.setStatus('Idle');
this.setRunState(false);
this.updateRunButton();
this.addDefaultStartNode();
this.upgradeLegacyNodes(true);

Expand Down Expand Up @@ -115,23 +114,56 @@
}

getNodeWidth(node) {
if (!node || !node.data) return COLLAPSED_NODE_WIDTH;
return node.data.collapsed ? COLLAPSED_NODE_WIDTH : EXPANDED_NODE_WIDTH;
if (!node) return DEFAULT_NODE_WIDTH;

// If expanded, use fixed expanded width
if (node.data && !node.data.collapsed) {
return EXPANDED_NODE_WIDTH;
}

// Try to get actual width from DOM
const el = document.getElementById(node.id);
if (el) {
return el.offsetWidth || DEFAULT_NODE_WIDTH;
}

return DEFAULT_NODE_WIDTH;
}

setStatus(text) {
if (this.chatStatusEl) {
this.chatStatusEl.innerText = text;
}
setWorkflowState(state) {
this.workflowState = state;
this.updateRunButton();
}

setRunState(isRunning) {
this.isRunning = isRunning;
if (this.runButton) {
this.runButton.disabled = isRunning;
updateRunButton() {
if (!this.runButton) return;

switch (this.workflowState) {
case 'running':
this.runButton.textContent = 'Running...';
this.runButton.disabled = true;
break;
case 'paused':
this.runButton.textContent = 'Paused';
this.runButton.disabled = true;
break;
case 'idle':
default:
this.runButton.textContent = 'Run Workflow';
this.runButton.disabled = false;
break;
}
}

appendStatusMessage(text, type = '') {
if (!this.chatMessages) return;
const message = document.createElement('div');
message.className = `chat-message status ${type}`;
message.textContent = text;
this.chatMessages.appendChild(message);
this.chatMessages.scrollTop = this.chatMessages.scrollHeight;
}

logManualUserMessage(text) {
this.appendChatMessage(text, 'user');
if (!this.runHistory) this.runHistory = [];
Expand Down Expand Up @@ -322,10 +354,11 @@
this.render();
this.addDefaultStartNode();
this.currentPrompt = '';
this.currentRunId = null;
if (this.chatMessages) {
this.chatMessages.innerHTML = '<div class="chat-message system">Canvas cleared. Start building your next workflow.</div>';
}
this.setStatus('Idle');
this.setWorkflowState('idle');
});

if (this.approveBtn) {
Expand Down Expand Up @@ -374,7 +407,7 @@
initWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const ws = new WebSocket(`${protocol}//${window.location.host}`);
ws.onmessage = (event) => {

Check warning on line 410 in apps/web/src/app/workflow-editor.ts

View workflow job for this annotation

GitHub Actions / build-and-test (20.x)

'event' is defined but never used. Allowed unused args must match /^_/u
// Placeholder for future real-time feedback
};
}
Expand Down Expand Up @@ -470,12 +503,17 @@
}

getDefaultStartPosition() {
const container = this.canvasStage || this.canvas;
const fallback = { x: 160, y: 160 };
const container = this.canvas;
const fallback = { x: 200, y: 200 };
if (!container) return fallback;
const rect = container.getBoundingClientRect();
const x = rect.width ? Math.max(60, rect.width * 0.2) : fallback.x;
const y = rect.height ? Math.max(60, rect.height * 0.3) : fallback.y;
if (!rect.width || !rect.height) return fallback;

// Center the node accounting for approximate start node width and height
const nodeWidth = 120; // Start node is narrow
const nodeHeight = 60;
const x = (rect.width / 2) - (nodeWidth / 2);
const y = (rect.height / 2) - (nodeHeight / 2);
return { x, y };
}

Expand Down Expand Up @@ -775,16 +813,19 @@

toolItems.forEach(tool => {
const row = document.createElement('label');
row.className = 'row';
row.className = 'tool-item';
const checkbox = document.createElement('input');
checkbox.type = 'checkbox';
checkbox.className = 'tool-checkbox';
checkbox.checked = node.data.tools?.[tool.key] || false;
checkbox.addEventListener('change', (e) => {
if (!node.data.tools) node.data.tools = {};
node.data.tools[tool.key] = e.target.checked;
});
row.appendChild(checkbox);
row.appendChild(document.createTextNode(` ${tool.label}`));
const labelText = document.createElement('span');
labelText.textContent = tool.label;
row.appendChild(labelText);
toolsList.appendChild(row);
});

Expand Down Expand Up @@ -1169,16 +1210,17 @@
}

async runWorkflow() {
// Don't start new workflow if already running or paused
if (this.workflowState !== 'idle') return;

this.upgradeLegacyNodes();
const startNode = this.nodes.find(n => n.type === 'start');
if (!startNode) {
alert('Add a Start node and connect your workflow before running.');
this.setStatus('Missing start node');
return;
}

this.setStatus('Running');
this.setRunState(true);
this.setWorkflowState('running');

this.currentPrompt = this.initialPrompt.value || '';
this.startChatSession(this.currentPrompt);
Expand All @@ -1197,9 +1239,9 @@

} catch (e) {
this.appendChatMessage('Error: ' + e.message, 'error');
this.setStatus('Failed');
this.appendStatusMessage('Failed', 'failed');
this.hideAgentSpinner();
this.setRunState(false);
this.setWorkflowState('idle');
}
}

Expand All @@ -1212,18 +1254,24 @@
this.currentRunId = result.runId;
const pausedNodeId = result.currentNodeId || this.extractWaitingNodeId(result.logs);
this.showApprovalMessage(pausedNodeId);
this.setStatus('Waiting for approval');
this.setWorkflowState('paused');
} else if (result.status === 'completed') {
this.clearApprovalMessage();
this.setStatus('Completed');
this.appendStatusMessage('Completed', 'completed');
this.hideAgentSpinner();
this.setRunState(false);
} else {
this.setWorkflowState('idle');
this.currentRunId = null;
} else if (result.status === 'failed') {
this.clearApprovalMessage();
this.appendStatusMessage('Failed', 'failed');
this.hideAgentSpinner();
this.setWorkflowState('idle');
this.currentRunId = null;
} else {
this.clearApprovalMessage();
this.setStatus(result.status || 'Idle');
if (result.status !== 'paused') {
this.hideAgentSpinner();
this.setRunState(false);
this.setWorkflowState('idle');
}
}
}
Expand All @@ -1233,16 +1281,17 @@
this.setApprovalButtonsDisabled(true);
const note = '';
this.replaceApprovalWithResult(decision, note);
this.setStatus('Running');
this.setWorkflowState('running');
this.showAgentSpinner();

try {
const result = await resumeWorkflow(this.currentRunId, { decision, note });
this.handleRunResult(result);
} catch (e) {
this.appendChatMessage(e.message, 'error');
this.appendStatusMessage('Failed', 'failed');
this.hideAgentSpinner();
this.setRunState(false);
this.setWorkflowState('idle');
}
}
}
Expand Down
Loading