-
@@ -52,1131 +103,145 @@
(function () {
"use strict";
- function notify(message, type) {
- type = type || "info";
- if (window.GBAlerts) {
- if (type === "success") {
- window.GBAlerts.info("Chat", message);
- } else if (type === "error") {
- window.GBAlerts.warning("Chat", message);
- } else {
- window.GBAlerts.info("Chat", message);
- }
- }
+ function initVibe() {
+ setupPipelineTabs();
+ setupPrompt();
+ setupSidebarCollapse();
+ setupWorkspaceAccordions();
+ setupDragAndDrop();
}
- var WS_BASE_URL =
- window.location.protocol === "https:" ? "wss://" : "ws://";
- var WS_URL = WS_BASE_URL + window.location.host + "/ws/chat";
-
- var MessageType = {
- EXTERNAL: 0,
- USER: 1,
- BOT_RESPONSE: 2,
- CONTINUE: 3,
- SUGGESTION: 4,
- CONTEXT_CHANGE: 5,
- };
-
- var EntityTypes = {
- lead: { icon: "👤", color: "#4CAF50", label: "Lead", route: "crm" },
- opportunity: {
- icon: "💰",
- color: "#FF9800",
- label: "Opportunity",
- route: "crm",
- },
- account: {
- icon: "🏢",
- color: "#2196F3",
- label: "Account",
- route: "crm",
- },
- contact: {
- icon: "📇",
- color: "#9C27B0",
- label: "Contact",
- route: "crm",
- },
- invoice: {
- icon: "📄",
- color: "#F44336",
- label: "Invoice",
- route: "billing",
- },
- quote: {
- icon: "📋",
- color: "#607D8B",
- label: "Quote",
- route: "billing",
- },
- case: {
- icon: "🎫",
- color: "#E91E63",
- label: "Case",
- route: "tickets",
- },
- product: {
- icon: "📦",
- color: "#795548",
- label: "Product",
- route: "products",
- },
- service: {
- icon: "⚙️",
- color: "#00BCD4",
- label: "Service",
- route: "products",
- },
- };
-
- var ws = null;
- var currentSessionId = null;
- var currentUserId = null;
- var currentBotId = "default";
- var currentBotName = "default";
- var isStreaming = false;
- var streamingMessageId = null;
- var currentStreamingContent = "";
- var reconnectAttempts = 0;
- var maxReconnectAttempts = 5;
- var disconnectNotified = false;
- var isUserScrolling = false;
-
- var mentionState = {
- active: false,
- query: "",
- startPos: -1,
- selectedIndex: 0,
- results: [],
- };
-
- function escapeHtml(text) {
- var div = document.createElement("div");
- div.textContent = text;
- return div.innerHTML;
- }
-
- // Scroll handling
- function scrollToBottom(animate) {
- var messages = document.getElementById("messages");
- if (messages) {
- if (animate) {
- messages.scrollTo({
- top: messages.scrollHeight,
- behavior: "smooth",
- });
- } else {
- messages.scrollTop = messages.scrollHeight;
- }
- }
- }
-
- function updateScrollButton() {
- var messages = document.getElementById("messages");
- var scrollBtn = document.getElementById("scrollToBottom");
- if (!messages || !scrollBtn) return;
-
- var isNearBottom =
- messages.scrollHeight - messages.scrollTop - messages.clientHeight <
- 100;
-
- if (isNearBottom) {
- scrollBtn.classList.remove("visible");
- } else {
- scrollBtn.classList.add("visible");
- }
- }
-
- // Scroll-to-bottom button click
- var scrollBtn = document.getElementById("scrollToBottom");
- if (scrollBtn) {
- scrollBtn.addEventListener("click", function () {
- scrollToBottom(true);
- isUserScrolling = false;
- });
- }
-
- // Detect user scrolling
- var messagesEl = document.getElementById("messages");
- if (messagesEl) {
- messagesEl.addEventListener("scroll", function () {
- isUserScrolling = true;
- updateScrollButton();
-
- // Reset isUserScrolling after 2 seconds of no scrolling
- clearTimeout(messagesEl.scrollTimeout);
- messagesEl.scrollTimeout = setTimeout(function () {
- isUserScrolling = false;
- }, 2000);
- });
- }
-
- function renderMentionInMessage(content) {
- return content.replace(
- /@(\w+):([^\s]+)/g,
- function (match, type, name) {
- var entityType = EntityTypes[type.toLowerCase()];
- if (entityType) {
- return (
- '
' +
- '' +
- entityType.icon +
- "" +
- '@' +
- type +
- ":" +
- escapeHtml(name) +
- "" +
- ""
- );
- }
- return match;
- },
- );
- }
-
- function addMessage(sender, content, msgId) {
- var messages = document.getElementById("messages");
- if (!messages) return;
-
- var div = document.createElement("div");
- div.className = "message " + sender;
- if (msgId) div.id = msgId;
-
- if (sender === "user") {
- var processedContent = renderMentionInMessage(
- escapeHtml(content),
- );
- div.innerHTML =
- '
' +
- processedContent +
- "
";
- } else {
- var parsed =
- typeof marked !== "undefined" && marked.parse
- ? marked.parse(content)
- : escapeHtml(content);
- parsed = renderMentionInMessage(parsed);
- div.innerHTML =
- '
' +
- parsed +
- "
";
- }
-
- messages.appendChild(div);
-
- // Auto-scroll to bottom unless user is manually scrolling
- if (!isUserScrolling) {
- scrollToBottom(true);
- } else {
- updateScrollButton();
- }
-
- setupMentionClickHandlers(div);
- }
-
- function setupMentionClickHandlers(container) {
- var mentions = container.querySelectorAll(".mention-tag");
- mentions.forEach(function (mention) {
- mention.addEventListener("click", function (e) {
- e.preventDefault();
- var type = this.getAttribute("data-type");
- var name = this.getAttribute("data-name");
- navigateToEntity(type, name);
- });
-
- mention.addEventListener("mouseenter", function (e) {
- var type = this.getAttribute("data-type");
- var name = this.getAttribute("data-name");
- showEntityCard(type, name, e.target);
- });
-
- mention.addEventListener("mouseleave", function () {
- hideEntityCard();
- });
- });
- }
-
- function navigateToEntity(type, name) {
- var entityType = EntityTypes[type.toLowerCase()];
- if (entityType) {
- var route = entityType.route;
- window.location.hash = "#" + route;
-
- var htmxLink = document.querySelector(
- 'a[data-section="' + route + '"]',
- );
- if (htmxLink) {
- htmx.trigger(htmxLink, "click");
- }
- }
- }
-
- function showEntityCard(type, name, targetEl) {
- var card = document.getElementById("entityCardTooltip");
- var entityType = EntityTypes[type.toLowerCase()];
- if (!card || !entityType) return;
-
- card.querySelector(".entity-card-type").textContent =
- entityType.label;
- card.querySelector(".entity-card-type").style.background =
- entityType.color;
- card.querySelector(".entity-card-title").textContent =
- entityType.icon + " " + name;
- card.querySelector(".entity-card-status").textContent = "";
- card.querySelector(".entity-card-details").textContent =
- "Loading...";
-
- var rect = targetEl.getBoundingClientRect();
- card.style.left = rect.left + "px";
- card.style.top = rect.top - card.offsetHeight - 8 + "px";
- card.classList.add("visible");
-
- fetchEntityDetails(type, name).then(function (details) {
- if (card.classList.contains("visible")) {
- card.querySelector(".entity-card-details").innerHTML =
- details;
- }
- });
- }
-
- function hideEntityCard() {
- var card = document.getElementById("entityCardTooltip");
- if (card) {
- card.classList.remove("visible");
- }
- }
-
- function fetchEntityDetails(type, name) {
- return fetch(
- "/api/search/entity?type=" +
- encodeURIComponent(type) +
- "&name=" +
- encodeURIComponent(name),
- )
- .then(function (r) {
- return r.json();
- })
- .then(function (data) {
- if (data && data.details) {
- return data.details;
- }
- return "No additional details available";
- })
- .catch(function () {
- return "Unable to load details";
- });
- }
-
- function showMentionDropdown() {
- var dropdown = document.getElementById("mentionDropdown");
- if (dropdown) {
- dropdown.classList.add("visible");
- }
- }
-
- function hideMentionDropdown() {
- var dropdown = document.getElementById("mentionDropdown");
- if (dropdown) {
- dropdown.classList.remove("visible");
- }
- mentionState.active = false;
- mentionState.query = "";
- mentionState.startPos = -1;
- mentionState.selectedIndex = 0;
- mentionState.results = [];
- }
-
- function searchEntities(query) {
- if (!query || query.length < 1) {
- var defaultResults = Object.keys(EntityTypes).map(
- function (type) {
- return {
- type: type,
- name: EntityTypes[type].label,
- icon: EntityTypes[type].icon,
- isTypeHint: true,
- };
- },
- );
- renderMentionResults(defaultResults);
- return;
- }
-
- var colonIndex = query.indexOf(":");
- if (colonIndex > 0) {
- var entityType = query.substring(0, colonIndex).toLowerCase();
- var searchTerm = query.substring(colonIndex + 1);
-
- if (EntityTypes[entityType]) {
- fetchEntitiesOfType(entityType, searchTerm);
- return;
- }
- }
-
- var filteredTypes = Object.keys(EntityTypes)
- .filter(function (type) {
- return (
- type.toLowerCase().indexOf(query.toLowerCase()) === 0 ||
- EntityTypes[type].label
- .toLowerCase()
- .indexOf(query.toLowerCase()) === 0
- );
- })
- .map(function (type) {
- return {
- type: type,
- name: EntityTypes[type].label,
- icon: EntityTypes[type].icon,
- isTypeHint: true,
- };
- });
-
- renderMentionResults(filteredTypes);
- }
-
- function fetchEntitiesOfType(type, searchTerm) {
- fetch(
- "/api/search/entities?type=" +
- encodeURIComponent(type) +
- "&q=" +
- encodeURIComponent(searchTerm || ""),
- )
- .then(function (r) {
- return r.json();
- })
- .then(function (data) {
- var results = (data.results || []).map(function (item) {
- return {
- type: type,
- name: item.name || item.title || item.number,
- id: item.id,
- icon: EntityTypes[type].icon,
- subtitle: item.subtitle || item.status || "",
- isTypeHint: false,
- };
- });
-
- if (results.length === 0) {
- results = [
- {
- type: type,
- name: "No results for '" + searchTerm + "'",
- icon: "❌",
- isTypeHint: false,
- disabled: true,
- },
- ];
- }
-
- renderMentionResults(results);
- })
- .catch(function () {
- renderMentionResults([
- {
- type: type,
- name: "Search unavailable",
- icon: "⚠️",
- isTypeHint: false,
- disabled: true,
- },
- ]);
- });
- }
-
- function renderMentionResults(results) {
- var container = document.getElementById("mentionResults");
+ function setupPipelineTabs() {
+ var container = document.querySelector(".vibe-pipeline");
if (!container) return;
- mentionState.results = results;
- mentionState.selectedIndex = 0;
+ 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");
+ });
+ tab.classList.add("active");
+ });
+ }
- container.innerHTML = results
- .map(function (item, index) {
- var classes = "mention-item";
- if (index === mentionState.selectedIndex)
- classes += " selected";
- if (item.disabled) classes += " disabled";
+ function setupPrompt() {
+ var input = document.getElementById("vibePromptInput");
+ var btn = document.getElementById("vibePromptBtn");
+ if (!input || !btn) return;
- var subtitle = item.subtitle
- ? '
' +
- escapeHtml(item.subtitle) +
- ""
- : "";
- var hint = item.isTypeHint
- ? '
Type : to search'
- : "";
+ function submit() {
+ var text = input.value.trim();
+ if (!text) return;
- return (
- '
' +
- '' +
- item.icon +
- "" +
- '' +
- '' +
- escapeHtml(item.name) +
- "" +
- subtitle +
- hint +
- "" +
- "
"
- );
+ btn.disabled = true;
+ btn.textContent = "Building...";
+
+ fetch("/api/autotask/classify", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ intent: text })
})
- .join("");
-
- container
- .querySelectorAll(".mention-item:not(.disabled)")
- .forEach(function (item) {
- item.addEventListener("click", function () {
- selectMentionItem(
- parseInt(this.getAttribute("data-index")),
- );
+ .then(function (r) { return r.json(); })
+ .then(function (data) {
+ btn.disabled = false;
+ btn.textContent = "Build";
+ if (data.result && data.result.app_url) {
+ showPreview(data.result.app_url);
+ } else if (data.app_url) {
+ showPreview(data.app_url);
+ } else if (data.error) {
+ alert("Error: " + data.error);
+ }
+ })
+ .catch(function () {
+ btn.disabled = false;
+ btn.textContent = "Build";
});
- });
- }
-
- function selectMentionItem(index) {
- var item = mentionState.results[index];
- if (!item || item.disabled) return;
-
- var input = document.getElementById("messageInput");
- if (!input) return;
-
- var value = input.value;
- var beforeMention = value.substring(0, mentionState.startPos);
- var afterMention = value.substring(input.selectionStart);
-
- var insertText;
- if (item.isTypeHint) {
- insertText = "@" + item.type + ":";
- mentionState.query = item.type + ":";
- mentionState.startPos = beforeMention.length;
- input.value = beforeMention + insertText + afterMention;
- input.setSelectionRange(
- beforeMention.length + insertText.length,
- beforeMention.length + insertText.length,
- );
- searchEntities(mentionState.query);
- return;
- } else {
- insertText = "@" + item.type + ":" + item.name + " ";
- input.value = beforeMention + insertText + afterMention;
- input.setSelectionRange(
- beforeMention.length + insertText.length,
- beforeMention.length + insertText.length,
- );
- hideMentionDropdown();
}
- input.focus();
- }
-
- function updateMentionSelection(direction) {
- var enabledResults = mentionState.results.filter(function (r) {
- return !r.disabled;
- });
- if (enabledResults.length === 0) return;
-
- var currentEnabled = 0;
- for (var i = 0; i < mentionState.selectedIndex; i++) {
- if (!mentionState.results[i].disabled) currentEnabled++;
- }
-
- currentEnabled += direction;
- if (currentEnabled < 0) currentEnabled = enabledResults.length - 1;
- if (currentEnabled >= enabledResults.length) currentEnabled = 0;
-
- var newIndex = 0;
- var count = 0;
- for (var j = 0; j < mentionState.results.length; j++) {
- if (!mentionState.results[j].disabled) {
- if (count === currentEnabled) {
- newIndex = j;
- break;
- }
- count++;
- }
- }
-
- mentionState.selectedIndex = newIndex;
-
- var items = document.querySelectorAll(
- "#mentionResults .mention-item",
- );
- items.forEach(function (item, idx) {
- item.classList.toggle("selected", idx === newIndex);
- });
-
- var selectedItem = document.querySelector(
- "#mentionResults .mention-item.selected",
- );
- if (selectedItem) {
- selectedItem.scrollIntoView({ block: "nearest" });
- }
- }
-
- function handleMentionInput(e) {
- var input = e.target;
- var value = input.value;
- var cursorPos = input.selectionStart;
-
- var textBeforeCursor = value.substring(0, cursorPos);
- var atIndex = textBeforeCursor.lastIndexOf("@");
-
- if (atIndex >= 0) {
- var charBeforeAt =
- atIndex > 0 ? textBeforeCursor[atIndex - 1] : " ";
- if (charBeforeAt === " " || atIndex === 0) {
- var query = textBeforeCursor.substring(atIndex + 1);
-
- if (!query.includes(" ")) {
- mentionState.active = true;
- mentionState.startPos = atIndex;
- mentionState.query = query;
- showMentionDropdown();
- searchEntities(query);
- return;
- }
- }
- }
-
- if (mentionState.active) {
- hideMentionDropdown();
- }
- }
-
- function handleMentionKeydown(e) {
- if (!mentionState.active) return false;
-
- if (e.key === "ArrowDown") {
- e.preventDefault();
- updateMentionSelection(1);
- return true;
- }
- if (e.key === "ArrowUp") {
- e.preventDefault();
- updateMentionSelection(-1);
- return true;
- }
- if (e.key === "Enter" || e.key === "Tab") {
- e.preventDefault();
- selectMentionItem(mentionState.selectedIndex);
- return true;
- }
- if (e.key === "Escape") {
- e.preventDefault();
- hideMentionDropdown();
- return true;
- }
-
- return false;
- }
-
- function updateStreaming(content) {
- var el = document.getElementById(streamingMessageId);
- if (el) {
- var parsed =
- typeof marked !== "undefined" && marked.parse
- ? marked.parse(content)
- : escapeHtml(content);
- parsed = renderMentionInMessage(parsed);
- el.querySelector(".message-content").innerHTML = parsed;
- }
- }
-
- function finalizeStreaming() {
- var el = document.getElementById(streamingMessageId);
- if (el) {
- var parsed =
- typeof marked !== "undefined" && marked.parse
- ? marked.parse(currentStreamingContent)
- : escapeHtml(currentStreamingContent);
- parsed = renderMentionInMessage(parsed);
- el.querySelector(".message-content").innerHTML = parsed;
- el.removeAttribute("id");
- setupMentionClickHandlers(el);
- }
- streamingMessageId = null;
- currentStreamingContent = "";
- }
-
- function processMessage(data) {
- if (data.is_complete) {
- if (isStreaming) {
- finalizeStreaming();
- } else {
- if (data.content && data.content.trim() !== "") {
- addMessage("bot", data.content);
- }
- }
- isStreaming = false;
-
- // Render suggestions when message is complete
- if (data.suggestions && Array.isArray(data.suggestions) && data.suggestions.length > 0) {
- renderSuggestions(data.suggestions);
- }
- } else {
- if (!isStreaming) {
- isStreaming = true;
- streamingMessageId = "streaming-" + Date.now();
- currentStreamingContent = data.content || "";
- addMessage(
- "bot",
- currentStreamingContent,
- streamingMessageId,
- );
- } else {
- currentStreamingContent += data.content || "";
- updateStreaming(currentStreamingContent);
- }
- }
- }
-
- // Render suggestion buttons
- function renderSuggestions(suggestions) {
- var suggestionsEl = document.getElementById("suggestions");
- if (!suggestionsEl) {
- console.warn("Suggestions container not found");
- return;
- }
-
- // Clear existing suggestions
- suggestionsEl.innerHTML = "";
-
- console.log("Rendering " + suggestions.length + " suggestions");
-
- suggestions.forEach(function (suggestion) {
- var chip = document.createElement("button");
- chip.className = "suggestion-chip";
- chip.textContent = suggestion.text || "Suggestion";
-
- // Use window.sendMessage which is already exposed
- chip.onclick = (function (sugg) {
- return function () {
- console.log("Suggestion clicked:", sugg);
- // Check if there's an action to parse
- if (sugg.action) {
- try {
- var action = typeof sugg.action === "string"
- ? JSON.parse(sugg.action)
- : sugg.action;
-
- console.log("Parsed action:", action);
-
- if (action.type === "invoke_tool") {
- // Send the display text so it shows correctly in chat
- // The backend will recognize this as a tool request
- window.sendMessage(sugg.text);
- } else if (action.type === "send_message") {
- window.sendMessage(action.message || sugg.text);
- } else if (action.type === "select_context") {
- window.sendMessage(action.context);
- } else {
- window.sendMessage(sugg.text);
- }
- } catch (e) {
- console.error("Failed to parse action:", e, "falling back to text");
- window.sendMessage(sugg.text);
- }
- } else {
- // No action, just send the text
- window.sendMessage(sugg.text);
- }
- };
- })(suggestion);
-
- suggestionsEl.appendChild(chip);
+ btn.addEventListener("click", submit);
+ input.addEventListener("keydown", function (e) {
+ if (e.key === "Enter") { e.preventDefault(); submit(); }
});
}
- function sendMessage(messageContent) {
- var input = document.getElementById("messageInput");
- if (!input) {
- console.error("Chat input not found");
- return;
- }
+ function showPreview(url) {
+ var preview = document.getElementById("vibePreview");
+ var urlBar = document.getElementById("vibePreviewUrl");
+ var content = document.getElementById("vibePreviewContent");
+ var empty = document.getElementById("vibeCanvasEmpty");
- // If no messageContent provided, read from input
- var content = messageContent || input.value.trim();
- if (!content) {
- return;
- }
-
- // If called from input field (no messageContent provided), clear input
- if (!messageContent) {
- hideMentionDropdown();
- input.value = "";
- input.focus();
- }
-
- addMessage("user", content);
-
- if (ws && ws.readyState === WebSocket.OPEN) {
- ws.send(
- JSON.stringify({
- bot_id: currentBotId,
- user_id: currentUserId,
- session_id: currentSessionId,
- channel: "web",
- content: content,
- message_type: MessageType.USER,
- timestamp: new Date().toISOString(),
- }),
- );
- } else {
- notify("Not connected to server. Message not sent.", "warning");
- }
+ if (empty) empty.style.display = "none";
+ if (preview) preview.style.display = "";
+ if (urlBar) urlBar.value = url;
+ if (content) content.innerHTML = '
';
}
- window.sendMessage = sendMessage;
+ function setupSidebarCollapse() {
+ var btn = document.getElementById("agentsSidebarCollapse");
+ var sidebar = document.getElementById("agentsSidebar");
+ if (!btn || !sidebar) return;
- // Expose session info for suggestion clicks
- window.getChatSessionInfo = function () {
- return {
- ws: ws,
- currentBotId: currentBotId,
- currentUserId: currentUserId,
- currentSessionId: currentSessionId,
- currentBotName: currentBotName
- };
- };
-
- function connectWebSocket() {
- if (ws) {
- ws.close();
- }
-
- updateConnectionStatus("connecting");
-
- var url =
- WS_URL +
- "?session_id=" +
- currentSessionId +
- "&user_id=" +
- currentUserId +
- "&bot_name=" +
- currentBotName;
- ws = new WebSocket(url);
-
- ws.onopen = function () {
- console.log("WebSocket connected");
- disconnectNotified = false;
- updateConnectionStatus("connected");
- };
-
- ws.onmessage = function (event) {
- try {
- var data = JSON.parse(event.data);
- console.log("Chat WebSocket received:", data);
-
- // Ignore connection confirmation
- if (data.type === "connected") {
- reconnectAttempts = 0;
- return;
- }
-
- // Process system events (theme changes, etc)
- if (data.event) {
- if (data.event === "change_theme") {
- applyThemeData(data.data || {});
- }
- return;
- }
-
- // Check if content contains theme change events (JSON strings)
- if (data.content && typeof data.content === "string") {
- try {
- var contentObj = JSON.parse(data.content);
- if (contentObj.event === "change_theme") {
- applyThemeData(contentObj.data || {});
- return;
- }
- } catch (e) {
- // Content is not JSON, continue processing
- }
- }
-
- // Only process bot responses
- if (data.message_type === MessageType.BOT_RESPONSE) {
- console.log("Processing bot response:", data);
- processMessage(data);
- } else {
- console.log("Ignoring non-bot message:", data);
- }
- } catch (e) {
- console.error("WS message error:", e);
- }
- };
-
- ws.onclose = function () {
- updateConnectionStatus("disconnected");
- if (!disconnectNotified) {
- notify("Disconnected from chat server", "error");
- disconnectNotified = true;
- }
- if (reconnectAttempts < maxReconnectAttempts) {
- reconnectAttempts++;
- updateConnectionStatus("connecting");
- setTimeout(connectWebSocket, 1000 * reconnectAttempts);
- }
- };
-
- ws.onerror = function (e) {
- console.error("WebSocket error:", e);
- updateConnectionStatus("disconnected");
- };
+ btn.addEventListener("click", function () {
+ sidebar.classList.toggle("collapsed");
+ btn.textContent = sidebar.classList.contains("collapsed") ? "▶" : "◀";
+ });
}
- // Apply theme data from WebSocket events
- function getContrastYIQ(hexcolor) {
- if (!hexcolor) return '#ffffff';
-
- // Handle named colors and variables by letting the browser resolve them
- var temp = document.createElement("div");
- temp.style.color = hexcolor;
- temp.style.display = "none";
- document.body.appendChild(temp);
- var style = window.getComputedStyle(temp).color;
- document.body.removeChild(temp);
-
- var rgb = style.match(/\d+/g);
- if (!rgb || rgb.length < 3) return '#ffffff';
-
- var r = parseInt(rgb[0]);
- var g = parseInt(rgb[1]);
- var b = parseInt(rgb[2]);
-
- var yiq = ((r * 299) + (g * 587) + (b * 114)) / 1000;
- return (yiq >= 128) ? '#000000' : '#ffffff';
- }
-
- function applyThemeData(themeData) {
- console.log("Applying theme data:", themeData);
-
- var color1 = themeData.color1 || themeData.data?.color1 || "black";
- var color2 = themeData.color2 || themeData.data?.color2 || "white";
- var logo = themeData.logo_url || themeData.data?.logo_url || "";
- var title = themeData.title || themeData.data?.title || window.__INITIAL_BOT_NAME__ || "Chat";
-
- // Set CSS variables for colors on document element
- document.documentElement.style.setProperty("--chat-color1", color1);
- document.documentElement.style.setProperty("--chat-color2", color2);
- document.documentElement.style.setProperty("--suggestion-color", color1);
- document.documentElement.style.setProperty("--suggestion-bg", color2);
-
- // Also set on root for better cascading
- document.documentElement.style.setProperty("--color1", color1);
- document.documentElement.style.setProperty("--color2", color2);
-
- // Update suggestion button colors to match theme
- document.documentElement.style.setProperty("--primary", color1);
- document.documentElement.style.setProperty("--accent", color1);
-
- document.documentElement.style.setProperty("--chat-fg1", getContrastYIQ(color1));
- document.documentElement.style.setProperty("--chat-fg2", getContrastYIQ(color2));
-
- console.log("Theme applied:", { color1: color1, color2: color2, logo: logo, title: title });
- }
-
- // Load bot config and apply colors/logo
- function loadBotConfig() {
- var botName = window.__INITIAL_BOT_NAME__ || "default";
-
- fetch("/api/bot/config?bot_name=" + encodeURIComponent(botName))
- .then(function (response) {
- return response.json();
- })
- .then(function (config) {
- if (!config) return;
-
- // Get the theme manager's theme for this bot to check if user selected a different theme
- var botId = botName.toLowerCase();
- var botThemeKey = "gb-theme-" + botId;
- var botTheme = window.ThemeManager ? (
- // Get bot-specific theme from theme manager's mapping
- (window.ThemeManager.getAvailableThemes &&
- window.ThemeManager.getAvailableThemes().find(t => t.id === botId)) ||
- // Fallback to localStorage
- localStorage.getItem(botThemeKey)
- ) : localStorage.getItem(botThemeKey);
-
- // Check if bot config has a theme-base setting
- var configThemeBase = config.theme_base || config["theme-base"] || "light";
-
- // Only use bot config colors if:
- // 1. No theme has been explicitly selected by user (localStorage empty or default)
- // 2. AND the bot config's theme-base matches the current theme
- var localStorageTheme = localStorage.getItem(botThemeKey);
- var useBotConfigColors = !localStorageTheme ||
- localStorageTheme === "default" ||
- localStorageTheme === configThemeBase;
-
- // Apply colors from config (API returns snake_case)
- var color1 = config.theme_color1 || config["theme-color1"] || config["Theme Color"] || "#3b82f6";
- var color2 = config.theme_color2 || config["theme-color2"] || "#f5deb3";
- var title = config.theme_title || config["theme-title"] || botName;
- var logo = config.theme_logo || config["theme-logo"] || "";
-
- // Only set bot config colors if user hasn't selected a different theme
- if (useBotConfigColors) {
- document.documentElement.setAttribute("data-has-bot-colors", "true");
- document.documentElement.style.setProperty("--chat-color1", color1);
- document.documentElement.style.setProperty("--chat-color2", color2);
- document.documentElement.style.setProperty("--suggestion-color", color1);
- document.documentElement.style.setProperty("--suggestion-bg", color2);
- document.documentElement.style.setProperty("--color1", color1);
- document.documentElement.style.setProperty("--color2", color2);
- document.documentElement.style.setProperty("--primary", color1);
- document.documentElement.style.setProperty("--accent", color1);
- document.documentElement.style.setProperty("--chat-fg1", getContrastYIQ(color1));
- document.documentElement.style.setProperty("--chat-fg2", getContrastYIQ(color2));
- console.log("Bot config colors applied:", { color1: color1, color2: color2 });
- } else {
- console.log("Bot config colors skipped - user selected custom theme:", localStorageTheme);
+ 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 ? "▶" : "▼";
}
-
- // Update logo if provided
- if (logo) {
- var logoImg = document.querySelector(".logo-icon-img");
- if (logoImg) {
- logoImg.src = logo;
- logoImg.alt = title || botName;
- logoImg.style.display = "block";
- }
- // Hide the SVG logo when image logo is used
- var logoSvg = document.querySelector(".logo-icon-svg");
- if (logoSvg) {
- logoSvg.style.display = "none";
- }
- }
-
- console.log("Bot config loaded:", { color1: color1, color2: color2, title: title, logo: logo });
- })
- .catch(function (e) {
- console.log("Could not load bot config:", e);
});
+ });
}
- function initChat() {
- // Load bot config first
- loadBotConfig();
- // Just proceed with chat initialization - no auth check
- proceedWithChatInit();
- }
-
- function proceedWithChatInit() {
- var botName = window.__INITIAL_BOT_NAME__ || "default";
- fetch("/api/auth?bot_name=" + encodeURIComponent(botName))
- .then(function (response) {
- return response.json();
- })
- .then(function (auth) {
- currentUserId = auth.user_id;
- currentSessionId = auth.session_id;
- currentBotId = auth.bot_id || "default";
- currentBotName = botName;
- console.log("Auth:", {
- currentUserId: currentUserId,
- currentSessionId: currentSessionId,
- currentBotId: currentBotId,
- currentBotName: currentBotName,
- });
- connectWebSocket();
- })
- .catch(function (e) {
- console.error("Auth failed:", e);
- notify("Failed to connect to chat server", "error");
- setTimeout(proceedWithChatInit, 3000);
+ function setupDragAndDrop() {
+ var cards = document.querySelectorAll(".as-agent-card");
+ cards.forEach(function (card) {
+ card.addEventListener("dragstart", function (e) {
+ e.dataTransfer.setData("text/plain", card.getAttribute("data-agent-id"));
+ card.classList.add("dragging");
});
- }
+ card.addEventListener("dragend", function () {
+ card.classList.remove("dragging");
+ });
+ });
- function updateConnectionStatus(status) {
- var statusEl = document.getElementById("connectionStatus");
- if (!statusEl) return;
-
- statusEl.className = "connection-status " + status;
-
- var statusText = statusEl.querySelector(".connection-text");
- if (statusText) {
- switch (status) {
- case "connected":
- statusText.textContent = "Connected";
- statusEl.style.display = "none";
- break;
- case "disconnected":
- statusText.textContent = "Disconnected";
- statusEl.style.display = "flex";
- break;
- case "connecting":
- statusText.textContent = "Connecting...";
- statusEl.style.display = "flex";
- break;
- }
- }
- }
-
- function setupEventHandlers() {
- var form = document.getElementById("chatForm");
- var input = document.getElementById("messageInput");
- var sendBtn = document.getElementById("sendBtn");
-
- if (form) {
- form.onsubmit = function (e) {
+ var dropzones = document.querySelectorAll(".as-workspace-dropzone");
+ dropzones.forEach(function (zone) {
+ zone.addEventListener("dragover", function (e) {
e.preventDefault();
- sendMessage();
- return false;
- };
- }
-
- if (input) {
- input.addEventListener("input", handleMentionInput);
-
- input.onkeydown = function (e) {
- if (handleMentionKeydown(e)) {
- return;
- }
- if (e.key === "Enter" && !e.shiftKey) {
- e.preventDefault();
- sendMessage();
- }
- };
- }
-
- if (sendBtn) {
- sendBtn.onclick = function (e) {
+ zone.classList.add("drag-over");
+ });
+ zone.addEventListener("dragleave", function () {
+ zone.classList.remove("drag-over");
+ });
+ zone.addEventListener("drop", function (e) {
e.preventDefault();
- sendMessage();
- };
- }
+ zone.classList.remove("drag-over");
+ var agentId = e.dataTransfer.getData("text/plain");
- document.addEventListener("click", function (e) {
- if (
- !e.target.closest("#mentionDropdown") &&
- !e.target.closest("#messageInput")
- ) {
- hideMentionDropdown();
- }
+ var agentEl = document.createElement("div");
+ agentEl.className = "as-workspace-agent";
+ agentEl.textContent = "Agent #" + agentId;
+ zone.parentNode.insertBefore(agentEl, zone);
+ });
});
}
- setupEventHandlers();
- initChat();
-
- console.log("Chat module initialized with @ mentions support");
+ if (document.readyState === "loading") {
+ document.addEventListener("DOMContentLoaded", initVibe);
+ } else {
+ initVibe();
+ }
})();
\ No newline at end of file
diff --git a/ui/suite/tasks/autotask-scoped.css b/ui/suite/tasks/autotask-scoped.css
index 4dc0ed7..845f452 100644
--- a/ui/suite/tasks/autotask-scoped.css
+++ b/ui/suite/tasks/autotask-scoped.css
@@ -1,7 +1,7 @@
/* =============================================================================
AUTOTASK - SENTIENT THEME
Dark UI with Neon Yellow/Lime Accents (#CDFE00)
- Pixel-perfect match to Mantis Farm Dashboard
+ Pixel-perfect match to Agent Dashboard
============================================================================= */
/* =============================================================================
@@ -1166,10 +1166,12 @@ body:has(.autotask-container) {
}
@keyframes blink {
+
0%,
100% {
opacity: 1;
}
+
50% {
opacity: 0;
}
@@ -1224,10 +1226,12 @@ body:has(.autotask-container) {
}
@keyframes pulse-glow {
+
0%,
100% {
filter: drop-shadow(0 0 4px var(--sentient-accent-glow));
}
+
50% {
filter: drop-shadow(0 0 12px var(--sentient-accent));
}
@@ -1335,6 +1339,7 @@ body:has(.autotask-container) {
opacity: 0;
transform: translateY(-4px);
}
+
to {
opacity: 1;
transform: translateY(0);
@@ -1596,6 +1601,7 @@ body:has(.autotask-container) {
opacity: 0;
transform: translateX(20px);
}
+
to {
opacity: 1;
transform: translateX(0);
@@ -1709,4 +1715,4 @@ body:has(.autotask-container) {
width: auto;
bottom: 12px;
}
-}
+}
\ No newline at end of file
diff --git a/ui/suite/tasks/autotask.css b/ui/suite/tasks/autotask.css
index 5a38f42..b4ab5fa 100644
--- a/ui/suite/tasks/autotask.css
+++ b/ui/suite/tasks/autotask.css
@@ -1,7 +1,7 @@
/* =============================================================================
AUTOTASK - SENTIENT THEME
Dark UI with Neon Yellow/Lime Accents (#CDFE00)
- Pixel-perfect match to Mantis Farm Dashboard
+ Pixel-perfect match to Agent Dashboard
============================================================================= */
/* =============================================================================
@@ -1166,10 +1166,12 @@ body:has(.autotask-container) {
}
@keyframes blink {
+
0%,
100% {
opacity: 1;
}
+
50% {
opacity: 0;
}
@@ -1224,10 +1226,12 @@ body:has(.autotask-container) {
}
@keyframes pulse-glow {
+
0%,
100% {
filter: drop-shadow(0 0 4px var(--sentient-accent-glow));
}
+
50% {
filter: drop-shadow(0 0 12px var(--sentient-accent));
}
@@ -1335,6 +1339,7 @@ body:has(.autotask-container) {
opacity: 0;
transform: translateY(-4px);
}
+
to {
opacity: 1;
transform: translateY(0);
@@ -1596,6 +1601,7 @@ body:has(.autotask-container) {
opacity: 0;
transform: translateX(20px);
}
+
to {
opacity: 1;
transform: translateX(0);
@@ -1709,4 +1715,4 @@ body:has(.autotask-container) {
width: auto;
bottom: 12px;
}
-}
+}
\ No newline at end of file
diff --git a/ui/suite/tasks/task-window.css b/ui/suite/tasks/task-window.css
new file mode 100644
index 0000000..acdc1c2
--- /dev/null
+++ b/ui/suite/tasks/task-window.css
@@ -0,0 +1,411 @@
+/* Task Window — Unified Tasks Dashboard Styles */
+
+.tw-container {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ background: #fff;
+ font-family: 'Fira Code', monospace;
+}
+
+/* ============================================
+ TAB HEADER
+ ============================================ */
+
+.tw-tabs-header {
+ display: flex;
+ background: #f8f8f8;
+ border-bottom: 1px solid #f0f1f2;
+ min-height: 34px;
+ overflow-x: auto;
+}
+
+.tw-tab {
+ height: 34px;
+ min-width: 140px;
+ padding: 0 18px;
+ border: none;
+ border-right: 1px solid #f0f1f2;
+ background: transparent;
+ font-family: 'Fira Code', monospace;
+ font-size: 12px;
+ font-weight: 600;
+ color: #3b3b3b;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ transition: all 0.15s;
+ white-space: nowrap;
+}
+
+.tw-tab:hover {
+ background: #fff;
+}
+
+.tw-tab.active {
+ background: #84d669;
+ color: #fff;
+}
+
+.tw-tab-prefix {
+ opacity: 0.7;
+}
+
+.tw-tab .tw-tab-close {
+ margin-left: 8px;
+ opacity: 0.5;
+ font-size: 14px;
+ cursor: pointer;
+}
+
+.tw-tab .tw-tab-close:hover {
+ opacity: 1;
+}
+
+/* ============================================
+ PIPELINE TABS
+ ============================================ */
+
+.tw-pipeline-tabs {
+ display: flex;
+ background: #f8f8f8;
+ border-bottom: 1px solid #f0f1f2;
+}
+
+.tw-pipeline-tab {
+ flex: 1;
+ height: 32px;
+ border: none;
+ border-right: 1px solid #f0f1f2;
+ background: transparent;
+ font-family: 'Fira Code', monospace;
+ font-size: 11px;
+ font-weight: 500;
+ color: #888;
+ cursor: pointer;
+ transition: all 0.15s;
+}
+
+.tw-pipeline-tab:last-child {
+ border-right: none;
+}
+
+.tw-pipeline-tab:hover {
+ background: #fff;
+ color: #3b3b3b;
+}
+
+.tw-pipeline-tab.active {
+ background: #84d669;
+ color: #fff;
+}
+
+/* ============================================
+ FILTERS
+ ============================================ */
+
+.tw-filters {
+ display: flex;
+ gap: 8px;
+ padding: 12px 16px;
+ border-bottom: 1px solid #f0f1f2;
+}
+
+.tw-filter-chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 4px;
+ padding: 5px 12px;
+ border: 1px solid #e0e0e0;
+ border-radius: 16px;
+ background: #fff;
+ font-family: 'Fira Code', monospace;
+ font-size: 11px;
+ font-weight: 500;
+ color: #666;
+ cursor: pointer;
+ transition: all 0.15s;
+}
+
+.tw-filter-chip:hover {
+ border-color: #84d669;
+ color: #3b3b3b;
+}
+
+.tw-filter-chip.active {
+ background: #84d669;
+ color: #fff;
+ border-color: #84d669;
+}
+
+.tw-filter-icon {
+ font-size: 10px;
+}
+
+/* ============================================
+ INTENT BAR
+ ============================================ */
+
+.tw-intent-bar {
+ display: flex;
+ gap: 8px;
+ padding: 12px 16px;
+ border-bottom: 1px solid #f0f1f2;
+}
+
+.tw-intent-input {
+ flex: 1;
+ padding: 8px 14px;
+ border: 1px solid #e0e0e0;
+ border-radius: 6px;
+ font-family: 'Fira Code', monospace;
+ font-size: 13px;
+ color: #3b3b3b;
+ outline: none;
+ transition: border-color 0.15s;
+ background: #fff;
+}
+
+.tw-intent-input:focus {
+ border-color: #84d669;
+ box-shadow: 0 0 0 3px rgba(132, 214, 105, 0.15);
+}
+
+.tw-intent-input::placeholder {
+ color: #bbb;
+}
+
+.tw-intent-run {
+ padding: 8px 20px;
+ border: none;
+ border-radius: 6px;
+ background: #84d669;
+ color: #fff;
+ font-family: 'Fira Code', monospace;
+ font-size: 12px;
+ font-weight: 700;
+ cursor: pointer;
+ transition: all 0.15s;
+ letter-spacing: 0.5px;
+}
+
+.tw-intent-run:hover {
+ background: #72c458;
+ transform: translateY(-1px);
+ box-shadow: 0 2px 8px rgba(132, 214, 105, 0.3);
+}
+
+.tw-intent-run:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+ transform: none;
+}
+
+/* ============================================
+ SPLIT VIEW (task list + detail)
+ ============================================ */
+
+.tw-split-view {
+ flex: 1;
+ display: flex;
+ overflow: hidden;
+}
+
+.tw-task-list {
+ width: 280px;
+ min-width: 200px;
+ border-right: 1px solid #f0f1f2;
+ overflow-y: auto;
+}
+
+.tw-task-list-empty {
+ padding: 24px 16px;
+ text-align: center;
+ color: #bbb;
+ font-size: 13px;
+}
+
+.tw-task-detail {
+ flex: 1;
+ overflow-y: auto;
+ padding: 20px;
+}
+
+.tw-task-detail-empty {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ height: 100%;
+ text-align: center;
+ color: #888;
+ gap: 8px;
+}
+
+.tw-task-detail-empty p {
+ margin: 0;
+ font-size: 14px;
+}
+
+.tw-hint {
+ font-size: 12px;
+ color: #bbb;
+}
+
+/* ============================================
+ TASK LIST ITEMS
+ ============================================ */
+
+.tw-task-item {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ padding: 10px 16px;
+ border-bottom: 1px solid #f0f1f2;
+ cursor: pointer;
+ transition: background 0.15s;
+}
+
+.tw-task-item:hover {
+ background: #f8f8f8;
+}
+
+.tw-task-item.active {
+ background: rgba(132, 214, 105, 0.08);
+ border-left: 3px solid #84d669;
+}
+
+.tw-task-status-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ flex-shrink: 0;
+}
+
+.tw-task-status-dot.running {
+ background: #84d669;
+}
+
+.tw-task-status-dot.pending {
+ background: #f59e0b;
+}
+
+.tw-task-status-dot.completed {
+ background: #3b82f6;
+}
+
+.tw-task-status-dot.failed {
+ background: #ef4444;
+}
+
+.tw-task-status-dot.paused {
+ background: #888;
+}
+
+.tw-task-item-info {
+ flex: 1;
+ min-width: 0;
+}
+
+.tw-task-item-name {
+ font-size: 12px;
+ font-weight: 600;
+ color: #3b3b3b;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.tw-task-item-type {
+ font-size: 10px;
+ color: #888;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+/* ============================================
+ AGENT PROFILE VIEW
+ ============================================ */
+
+.tw-agent-profile {
+ padding: 20px;
+}
+
+.tw-agent-header {
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ margin-bottom: 24px;
+ padding-bottom: 16px;
+ border-bottom: 1px solid #f0f1f2;
+}
+
+.tw-agent-avatar {
+ width: 48px;
+ height: 48px;
+ border-radius: 50%;
+ background: #84d669;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ color: #fff;
+ font-size: 20px;
+ font-weight: 700;
+}
+
+.tw-agent-meta {
+ flex: 1;
+}
+
+.tw-agent-title {
+ font-size: 16px;
+ font-weight: 700;
+ color: #3b3b3b;
+ margin: 0 0 4px 0;
+}
+
+.tw-agent-subtitle {
+ font-size: 12px;
+ color: #888;
+}
+
+.tw-section {
+ margin-bottom: 20px;
+}
+
+.tw-section-title {
+ font-size: 11px;
+ font-weight: 700;
+ color: #888;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+ margin-bottom: 10px;
+}
+
+.tw-stat-grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
+ gap: 10px;
+}
+
+.tw-stat-card {
+ padding: 12px;
+ background: #f8f8f8;
+ border-radius: 8px;
+ border: 1px solid #f0f1f2;
+}
+
+.tw-stat-value {
+ font-size: 18px;
+ font-weight: 700;
+ color: #3b3b3b;
+}
+
+.tw-stat-label {
+ font-size: 10px;
+ color: #888;
+ text-transform: uppercase;
+ letter-spacing: 0.3px;
+ margin-top: 2px;
+}
\ No newline at end of file
diff --git a/ui/suite/tasks/task-window.html b/ui/suite/tasks/task-window.html
new file mode 100644
index 0000000..898bcb1
--- /dev/null
+++ b/ui/suite/tasks/task-window.html
@@ -0,0 +1,189 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Select a task
+
Click on a task from the list to view details.
+
Bot Database: All apps share the same database tables.
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ui/suite/vibe/agents-sidebar.css b/ui/suite/vibe/agents-sidebar.css
new file mode 100644
index 0000000..19c7db6
--- /dev/null
+++ b/ui/suite/vibe/agents-sidebar.css
@@ -0,0 +1,494 @@
+/* Agents & Workspaces Sidebar + Vibe Canvas Styles */
+
+/* ============================================
+ VIBE CONTAINER
+ ============================================ */
+
+.vibe-container {
+ display: flex;
+ flex-direction: column;
+ height: 100%;
+ background: #fff;
+ font-family: 'Fira Code', monospace;
+}
+
+/* ============================================
+ PIPELINE TABS
+ ============================================ */
+
+.vibe-pipeline {
+ display: flex;
+ background: #f8f8f8;
+ border-bottom: 1px solid #f0f1f2;
+}
+
+.vibe-pipeline-tab {
+ flex: 1;
+ height: 34px;
+ border: none;
+ border-right: 1px solid #f0f1f2;
+ background: transparent;
+ font-family: 'Fira Code', monospace;
+ font-size: 11px;
+ font-weight: 600;
+ color: #888;
+ cursor: pointer;
+ transition: all 0.15s;
+}
+
+.vibe-pipeline-tab:last-child {
+ border-right: none;
+}
+
+.vibe-pipeline-tab:hover {
+ background: #fff;
+ color: #3b3b3b;
+}
+
+.vibe-pipeline-tab.active {
+ background: #84d669;
+ color: #fff;
+}
+
+/* ============================================
+ VIBE BODY (Canvas + Sidebar)
+ ============================================ */
+
+.vibe-body {
+ flex: 1;
+ display: flex;
+ overflow: hidden;
+}
+
+/* ============================================
+ CANVAS AREA
+ ============================================ */
+
+.vibe-canvas {
+ flex: 1;
+ overflow-y: auto;
+ display: flex;
+ flex-direction: column;
+}
+
+.vibe-canvas-empty {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 12px;
+ padding: 40px;
+ text-align: center;
+}
+
+.vibe-canvas-icon {
+ font-size: 48px;
+}
+
+.vibe-canvas-empty h3 {
+ margin: 0;
+ font-size: 20px;
+ font-weight: 700;
+ color: #3b3b3b;
+}
+
+.vibe-canvas-empty p {
+ margin: 0;
+ font-size: 14px;
+ color: #888;
+ max-width: 400px;
+}
+
+.vibe-canvas-prompt {
+ display: flex;
+ gap: 8px;
+ margin-top: 12px;
+ width: 100%;
+ max-width: 500px;
+}
+
+.vibe-prompt-input {
+ flex: 1;
+ padding: 10px 16px;
+ border: 2px solid #e0e0e0;
+ border-radius: 8px;
+ font-family: 'Fira Code', monospace;
+ font-size: 13px;
+ color: #3b3b3b;
+ outline: none;
+ transition: border-color 0.2s;
+}
+
+.vibe-prompt-input:focus {
+ border-color: #84d669;
+ box-shadow: 0 0 0 3px rgba(132, 214, 105, 0.15);
+}
+
+.vibe-prompt-input::placeholder {
+ color: #bbb;
+}
+
+.vibe-prompt-btn {
+ padding: 10px 24px;
+ border: none;
+ border-radius: 8px;
+ background: #84d669;
+ color: #fff;
+ font-family: 'Fira Code', monospace;
+ font-size: 13px;
+ font-weight: 700;
+ cursor: pointer;
+ transition: all 0.15s;
+}
+
+.vibe-prompt-btn:hover {
+ background: #72c458;
+ transform: translateY(-1px);
+ box-shadow: 0 4px 12px rgba(132, 214, 105, 0.3);
+}
+
+.vibe-prompt-btn:disabled {
+ opacity: 0.6;
+ cursor: not-allowed;
+ transform: none;
+}
+
+/* Steps */
+.vibe-steps {
+ padding: 16px;
+}
+
+/* Preview */
+.vibe-preview {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ border-top: 1px solid #f0f1f2;
+}
+
+.vibe-preview-header {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding: 6px 12px;
+ background: #f8f8f8;
+ border-bottom: 1px solid #f0f1f2;
+ font-family: 'Fira Code', monospace;
+ font-size: 11px;
+ font-weight: 600;
+ color: #3b3b3b;
+}
+
+.vibe-preview-url {
+ flex: 1;
+ padding: 3px 10px;
+ background: #fff;
+ border: 1px solid #e0e0e0;
+ border-radius: 4px;
+ font-family: 'Fira Code', monospace;
+ font-size: 11px;
+ color: #666;
+}
+
+.vibe-preview-content {
+ flex: 1;
+}
+
+.vibe-preview-content iframe {
+ width: 100%;
+ height: 100%;
+ border: none;
+}
+
+/* ============================================
+ AGENTS & WORKSPACES SIDEBAR
+ ============================================ */
+
+.agents-sidebar {
+ width: 260px;
+ min-width: 260px;
+ background: #f8f8f8;
+ border-left: 1px solid #f0f1f2;
+ overflow-y: auto;
+ transition: width 0.25s, min-width 0.25s;
+}
+
+.agents-sidebar.collapsed {
+ width: 0;
+ min-width: 0;
+ overflow: hidden;
+}
+
+/* Section */
+.as-section {
+ border-bottom: 1px solid #f0f1f2;
+}
+
+.as-section-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: 12px 14px 8px 14px;
+}
+
+.as-section-header h3 {
+ margin: 0;
+ font-size: 11px;
+ font-weight: 700;
+ color: #888;
+ text-transform: uppercase;
+ letter-spacing: 0.5px;
+}
+
+.as-collapse-btn {
+ width: 20px;
+ height: 20px;
+ border: none;
+ background: transparent;
+ color: #888;
+ cursor: pointer;
+ font-size: 10px;
+ padding: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 4px;
+ transition: all 0.15s;
+}
+
+.as-collapse-btn:hover {
+ background: #e0e0e0;
+ color: #3b3b3b;
+}
+
+/* ============================================
+ AGENT CARDS
+ ============================================ */
+
+.as-agent-list {
+ padding: 0 8px;
+}
+
+.as-agent-card {
+ background: #fff;
+ border: 1px solid #f0f1f2;
+ border-radius: 8px;
+ padding: 10px 12px;
+ margin-bottom: 6px;
+ cursor: grab;
+ transition: all 0.15s;
+}
+
+.as-agent-card:hover {
+ border-color: #84d669;
+ box-shadow: 0 2px 8px rgba(132, 214, 105, 0.1);
+}
+
+.as-agent-card.dragging {
+ opacity: 0.5;
+ transform: scale(0.95);
+}
+
+.as-agent-header {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ margin-bottom: 6px;
+}
+
+.as-status-dot {
+ width: 8px;
+ height: 8px;
+ border-radius: 50%;
+ flex-shrink: 0;
+}
+
+.as-status-dot.green {
+ background: #84d669;
+}
+
+.as-status-dot.yellow {
+ background: #f59e0b;
+}
+
+.as-status-dot.red {
+ background: #ef4444;
+}
+
+.as-status-dot.gray {
+ background: #ccc;
+}
+
+.as-agent-name {
+ flex: 1;
+ font-size: 12px;
+ font-weight: 600;
+ color: #3b3b3b;
+}
+
+.as-drag-handle {
+ color: #ccc;
+ font-size: 14px;
+ cursor: grab;
+}
+
+.as-agent-body {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 6px;
+}
+
+.as-agent-icons {
+ font-size: 12px;
+ letter-spacing: 2px;
+}
+
+.as-badge {
+ display: inline-block;
+ padding: 1px 6px;
+ border-radius: 3px;
+ font-size: 9px;
+ font-weight: 700;
+ letter-spacing: 0.5px;
+ text-transform: uppercase;
+}
+
+.badge-evolved {
+ background: #84d669;
+ color: #fff;
+}
+
+.badge-bred {
+ background: #f59e0b;
+ color: #fff;
+}
+
+.badge-wild {
+ background: #ef4444;
+ color: #fff;
+}
+
+/* Quota Bar */
+.as-agent-bar {
+ height: 3px;
+ background: #f0f1f2;
+ border-radius: 2px;
+ overflow: hidden;
+}
+
+.as-bar-fill {
+ height: 100%;
+ background: #84d669;
+ border-radius: 2px;
+ transition: width 0.3s;
+}
+
+.as-bar-fill.bred {
+ background: #f59e0b;
+}
+
+.as-bar-fill.wild {
+ background: #ef4444;
+}
+
+/* Create Agent Button */
+.as-create-btn {
+ width: calc(100% - 16px);
+ margin: 8px 8px;
+ padding: 8px;
+ border: 2px dashed #e0e0e0;
+ border-radius: 8px;
+ background: transparent;
+ font-family: 'Fira Code', monospace;
+ font-size: 11px;
+ font-weight: 500;
+ color: #888;
+ cursor: pointer;
+ transition: all 0.15s;
+}
+
+.as-create-btn:hover {
+ border-color: #84d669;
+ color: #84d669;
+ background: rgba(132, 214, 105, 0.05);
+}
+
+/* ============================================
+ WORKSPACES
+ ============================================ */
+
+.as-workspace-list {
+ padding: 0 8px 8px 8px;
+}
+
+.as-workspace-item {
+ margin-bottom: 4px;
+}
+
+.as-workspace-toggle {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ width: 100%;
+ padding: 8px 10px;
+ border: none;
+ border-radius: 6px;
+ background: transparent;
+ font-family: 'Fira Code', monospace;
+ font-size: 12px;
+ font-weight: 500;
+ color: #3b3b3b;
+ cursor: pointer;
+ transition: background 0.15s;
+ text-align: left;
+}
+
+.as-workspace-toggle:hover {
+ background: #fff;
+}
+
+.as-workspace-arrow {
+ font-size: 8px;
+ color: #888;
+ width: 8px;
+ transition: transform 0.15s;
+}
+
+.as-workspace-count {
+ margin-left: auto;
+ font-size: 10px;
+ background: #e0e0e0;
+ color: #666;
+ padding: 1px 6px;
+ border-radius: 8px;
+}
+
+.as-workspace-body {
+ padding: 4px 0 4px 20px;
+}
+
+.as-workspace-agent {
+ padding: 4px 10px;
+ font-size: 11px;
+ color: #666;
+ border-left: 2px solid #f0f1f2;
+}
+
+.as-workspace-dropzone {
+ margin-top: 4px;
+ padding: 10px;
+ border: 2px dashed #e0e0e0;
+ border-radius: 6px;
+ text-align: center;
+ font-size: 11px;
+ color: #ccc;
+ transition: all 0.2s;
+}
+
+.as-workspace-dropzone.drag-over {
+ border-color: #84d669;
+ background: rgba(132, 214, 105, 0.05);
+ color: #84d669;
+}
\ No newline at end of file