feat: Split chat.html into modular JS modules for #495 switcher support
- Split partials/chat.html (1513→70 lines) into 8 JS modules: chat-state.js, chat-switchers.js, chat-mentions.js, chat-messages.js, chat-suggestions.js, chat-theme.js, chat-websocket.js, chat-init.js - Centralized state in ChatState global object - Switcher chips auto-activate on switch_context suggestion action - active_switchers sent in every WS message payload - Removed old chat-main.js (merged into modules) - Split vibe.html into vibe/ module directory with CSS extraction - Updated standalone chat/chat.html to use same modules
This commit is contained in:
parent
1bb96f1923
commit
28c48eeabf
21 changed files with 3184 additions and 3659 deletions
126
botui/ui/suite/chat/chat-init.js
Normal file
126
botui/ui/suite/chat/chat-init.js
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
function sendMessage(messageContent) {
|
||||
var input = document.getElementById("messageInput");
|
||||
if (!input) return;
|
||||
|
||||
var content = messageContent || input.value.trim();
|
||||
if (!content) return;
|
||||
|
||||
if (ChatState.isStreaming && ChatState.streamingMessageId) {
|
||||
finalizeStreaming();
|
||||
ChatState.isStreaming = false;
|
||||
}
|
||||
|
||||
if (!messageContent) {
|
||||
hideMentionDropdown();
|
||||
input.value = "";
|
||||
input.focus();
|
||||
}
|
||||
|
||||
addMessage("user", content);
|
||||
|
||||
if (ChatState.ws && ChatState.ws.readyState === WebSocket.OPEN) {
|
||||
ChatState.ws.send(JSON.stringify({
|
||||
bot_id: ChatState.currentBotId,
|
||||
user_id: ChatState.currentUserId,
|
||||
session_id: ChatState.currentSessionId,
|
||||
channel: "web",
|
||||
content: content,
|
||||
message_type: MessageType.USER,
|
||||
active_switchers: Array.from(ChatState.activeSwitchers),
|
||||
timestamp: new Date().toISOString(),
|
||||
}));
|
||||
} else {
|
||||
notify("Not connected to server. Message not sent.", "warning");
|
||||
}
|
||||
}
|
||||
|
||||
window.sendMessage = sendMessage;
|
||||
|
||||
window.getChatSessionInfo = function () {
|
||||
return {
|
||||
ws: ChatState.ws,
|
||||
currentBotId: ChatState.currentBotId,
|
||||
currentUserId: ChatState.currentUserId,
|
||||
currentSessionId: ChatState.currentSessionId,
|
||||
currentBotName: ChatState.currentBotName,
|
||||
};
|
||||
};
|
||||
|
||||
function proceedWithChatInit() {
|
||||
var botName = window.__INITIAL_BOT_NAME__ || "default";
|
||||
var storageKey = "gb_chat_" + botName;
|
||||
var stored = {};
|
||||
try { stored = JSON.parse(localStorage.getItem(storageKey) || "{}"); } catch (e) {}
|
||||
|
||||
var authUrl = "/api/auth?bot_name=" + encodeURIComponent(botName);
|
||||
if (stored.user_id) authUrl += "&user_id=" + encodeURIComponent(stored.user_id);
|
||||
if (stored.session_id) authUrl += "&session_id=" + encodeURIComponent(stored.session_id);
|
||||
|
||||
fetch(authUrl)
|
||||
.then(function (response) { return response.json(); })
|
||||
.then(function (auth) {
|
||||
ChatState.currentUserId = auth.user_id;
|
||||
ChatState.currentSessionId = auth.session_id;
|
||||
ChatState.currentBotId = auth.bot_id || "default";
|
||||
ChatState.currentBotName = botName;
|
||||
try {
|
||||
localStorage.setItem(storageKey, JSON.stringify({ user_id: auth.user_id, session_id: auth.session_id }));
|
||||
} catch (e) {}
|
||||
connectWebSocket();
|
||||
})
|
||||
.catch(function () {
|
||||
notify("Failed to connect to chat server", "error");
|
||||
setTimeout(proceedWithChatInit, 3000);
|
||||
});
|
||||
}
|
||||
|
||||
function setupEventHandlers() {
|
||||
var form = document.getElementById("chatForm");
|
||||
var input = document.getElementById("messageInput");
|
||||
var sendBtn = document.getElementById("sendBtn");
|
||||
|
||||
if (form) {
|
||||
form.onsubmit = 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) { e.preventDefault(); sendMessage(); };
|
||||
}
|
||||
|
||||
var scrollBtn = document.getElementById("scrollToBottom");
|
||||
if (scrollBtn) {
|
||||
scrollBtn.addEventListener("click", function () { scrollToBottom(true); ChatState.isUserScrolling = false; });
|
||||
}
|
||||
|
||||
var messagesEl = document.getElementById("messages");
|
||||
if (messagesEl) {
|
||||
messagesEl.addEventListener("scroll", function () {
|
||||
ChatState.isUserScrolling = true;
|
||||
updateScrollButton();
|
||||
clearTimeout(messagesEl.scrollTimeout);
|
||||
messagesEl.scrollTimeout = setTimeout(function () { ChatState.isUserScrolling = false; }, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("click", function (e) {
|
||||
if (!e.target.closest("#mentionDropdown") && !e.target.closest("#messageInput")) {
|
||||
hideMentionDropdown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function initChat() {
|
||||
loadBotConfig();
|
||||
proceedWithChatInit();
|
||||
}
|
||||
|
||||
setupEventHandlers();
|
||||
initChat();
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
// Chat Main Module - Initializes chat and coordinates between modules
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
function notify(message, type) {
|
||||
type = type || "info";
|
||||
if (window.GBAlerts) {
|
||||
window.GBAlerts.show(message, type);
|
||||
} else {
|
||||
console.log("[" + type + "]", message);
|
||||
}
|
||||
}
|
||||
|
||||
function initChat() {
|
||||
console.log("Chat module initialized");
|
||||
setupEventHandlers();
|
||||
}
|
||||
|
||||
function setupEventHandlers() {
|
||||
var form = document.getElementById("chatForm");
|
||||
var input = document.getElementById("messageInput");
|
||||
|
||||
if (form) {
|
||||
form.onsubmit = function (e) {
|
||||
e.preventDefault();
|
||||
sendMessage();
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
if (input) {
|
||||
input.addEventListener("input", handleMentionInput);
|
||||
}
|
||||
|
||||
var scrollBtn = document.getElementById("scrollToBottom");
|
||||
if (scrollBtn) {
|
||||
scrollBtn.addEventListener("click", function() {
|
||||
scrollToBottom(true);
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener("click", function (e) {
|
||||
if (
|
||||
!e.target.closest("#mentionDropdown") &&
|
||||
!e.target.closest("#messageInput")
|
||||
) {
|
||||
hideMentionDropdown();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sendMessage() {
|
||||
var input = document.getElementById("messageInput");
|
||||
if (!input) return;
|
||||
|
||||
var content = input.value.trim();
|
||||
if (!content) return;
|
||||
|
||||
// Get active switchers
|
||||
var activeSwitcherIds = getActiveSwitcherIds();
|
||||
console.log('Sending message with active_switchers:', activeSwitcherIds);
|
||||
|
||||
// Add user message
|
||||
addMessage("user", content);
|
||||
|
||||
// Clear input
|
||||
input.value = "";
|
||||
input.focus();
|
||||
|
||||
// Send via WebSocket
|
||||
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,
|
||||
active_switchers: activeSwitcherIds,
|
||||
timestamp: new Date().toISOString()
|
||||
}));
|
||||
} else {
|
||||
notify("Not connected to server. Message not sent.", "warning");
|
||||
}
|
||||
}
|
||||
|
||||
// Expose to global scope
|
||||
window.sendMessage = sendMessage;
|
||||
window.initChat = initChat;
|
||||
|
||||
// Initialize on load
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", initChat);
|
||||
} else {
|
||||
initChat();
|
||||
}
|
||||
})();
|
||||
249
botui/ui/suite/chat/chat-mentions.js
Normal file
249
botui/ui/suite/chat/chat-mentions.js
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
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");
|
||||
ChatState.mentionState.active = false;
|
||||
ChatState.mentionState.query = "";
|
||||
ChatState.mentionState.startPos = -1;
|
||||
ChatState.mentionState.selectedIndex = 0;
|
||||
ChatState.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: "\u274C", isTypeHint: false, disabled: true }];
|
||||
}
|
||||
renderMentionResults(results);
|
||||
})
|
||||
.catch(function () {
|
||||
renderMentionResults([{ type: type, name: "Search unavailable", icon: "\u26A0\uFE0F", isTypeHint: false, disabled: true }]);
|
||||
});
|
||||
}
|
||||
|
||||
function renderMentionResults(results) {
|
||||
var container = document.getElementById("mentionResults");
|
||||
if (!container) return;
|
||||
|
||||
ChatState.mentionState.results = results;
|
||||
ChatState.mentionState.selectedIndex = 0;
|
||||
|
||||
container.innerHTML = results.map(function (item, index) {
|
||||
var classes = "mention-item";
|
||||
if (index === ChatState.mentionState.selectedIndex) classes += " selected";
|
||||
if (item.disabled) classes += " disabled";
|
||||
var subtitle = item.subtitle ? '<span class="mention-item-subtitle">' + escapeHtml(item.subtitle) + "</span>" : "";
|
||||
var hint = item.isTypeHint ? '<span class="mention-item-hint">Type : to search</span>' : "";
|
||||
return '<div class="' + classes + '" data-index="' + index + '" data-type="' + item.type +
|
||||
'" data-name="' + escapeHtml(item.name) + '" data-is-type="' + item.isTypeHint + '">' +
|
||||
'<span class="mention-item-icon">' + item.icon + "</span>" +
|
||||
'<span class="mention-item-content">' +
|
||||
'<span class="mention-item-name">' + escapeHtml(item.name) + "</span>" +
|
||||
subtitle + hint + "</span></div>";
|
||||
}).join("");
|
||||
|
||||
container.querySelectorAll(".mention-item:not(.disabled)").forEach(function (item) {
|
||||
item.addEventListener("click", function () {
|
||||
selectMentionItem(parseInt(this.getAttribute("data-index")));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function selectMentionItem(index) {
|
||||
var item = ChatState.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, ChatState.mentionState.startPos);
|
||||
var afterMention = value.substring(input.selectionStart);
|
||||
|
||||
var insertText;
|
||||
if (item.isTypeHint) {
|
||||
insertText = "@" + item.type + ":";
|
||||
ChatState.mentionState.query = item.type + ":";
|
||||
ChatState.mentionState.startPos = beforeMention.length;
|
||||
input.value = beforeMention + insertText + afterMention;
|
||||
input.setSelectionRange(beforeMention.length + insertText.length, beforeMention.length + insertText.length);
|
||||
searchEntities(ChatState.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 = ChatState.mentionState.results.filter(function (r) { return !r.disabled; });
|
||||
if (enabledResults.length === 0) return;
|
||||
|
||||
var currentEnabled = 0;
|
||||
for (var i = 0; i < ChatState.mentionState.selectedIndex; i++) {
|
||||
if (!ChatState.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 < ChatState.mentionState.results.length; j++) {
|
||||
if (!ChatState.mentionState.results[j].disabled) {
|
||||
if (count === currentEnabled) { newIndex = j; break; }
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
ChatState.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(" ")) {
|
||||
ChatState.mentionState.active = true;
|
||||
ChatState.mentionState.startPos = atIndex;
|
||||
ChatState.mentionState.query = query;
|
||||
showMentionDropdown();
|
||||
searchEntities(query);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ChatState.mentionState.active) hideMentionDropdown();
|
||||
}
|
||||
|
||||
function handleMentionKeydown(e) {
|
||||
if (!ChatState.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(ChatState.mentionState.selectedIndex); return true; }
|
||||
if (e.key === "Escape") { e.preventDefault(); hideMentionDropdown(); return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
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 () {
|
||||
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) { return data && data.details ? data.details : "No additional details available"; })
|
||||
.catch(function () { return "Unable to load details"; });
|
||||
}
|
||||
|
|
@ -1,53 +1,165 @@
|
|||
// Chat Messages Module - Handles message rendering and display
|
||||
function addMessage(role, content, messageId) {
|
||||
var messages = document.getElementById("messages");
|
||||
if (!messages) return;
|
||||
|
||||
var messageDiv = document.createElement("div");
|
||||
messageDiv.className = "message message-" + role;
|
||||
if (messageId) {
|
||||
messageDiv.dataset.messageId = messageId;
|
||||
}
|
||||
|
||||
if (role === "bot") {
|
||||
messageDiv.innerHTML = marked.parse(content);
|
||||
} else {
|
||||
messageDiv.textContent = content;
|
||||
}
|
||||
|
||||
messages.appendChild(messageDiv);
|
||||
scrollToBottom(false);
|
||||
}
|
||||
|
||||
function scrollToBottom(animate) {
|
||||
var messages = document.getElementById("messages");
|
||||
if (!messages) return;
|
||||
|
||||
var messages = document.getElementById("messages");
|
||||
if (messages) {
|
||||
if (animate) {
|
||||
messages.scrollTo({
|
||||
top: messages.scrollHeight,
|
||||
behavior: "smooth"
|
||||
});
|
||||
messages.scrollTo({ top: messages.scrollHeight, behavior: "smooth" });
|
||||
} else {
|
||||
messages.scrollTop = messages.scrollHeight;
|
||||
messages.scrollTop = messages.scrollHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showThinking() {
|
||||
var messages = document.getElementById("messages");
|
||||
if (!messages) return;
|
||||
|
||||
var thinking = document.createElement("div");
|
||||
thinking.className = "message message-bot thinking";
|
||||
thinking.id = "thinking-indicator";
|
||||
thinking.textContent = "Thinking...";
|
||||
messages.appendChild(thinking);
|
||||
scrollToBottom(false);
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
function hideThinking() {
|
||||
var thinking = document.getElementById("thinking-indicator");
|
||||
if (thinking) {
|
||||
thinking.remove();
|
||||
function renderMentionInMessage(content) {
|
||||
return content.replace(/@(\w+):([^\s]+)/g, function (match, type, name) {
|
||||
var entityType = EntityTypes[type.toLowerCase()];
|
||||
if (entityType) {
|
||||
return '<span class="mention-tag" data-type="' + type + '" data-name="' + escapeHtml(name) + '">' +
|
||||
'<span class="mention-icon">' + entityType.icon + "</span>" +
|
||||
'<span class="mention-text">@' + type + ":" + escapeHtml(name) + "</span>" +
|
||||
"</span>";
|
||||
}
|
||||
return match;
|
||||
});
|
||||
}
|
||||
|
||||
function stripMarkdownBlocks(content) {
|
||||
var htmlMatch = content.match(/^```(?:html|xml)?\s*\n([\s\S]+?)\n?```$/i);
|
||||
if (htmlMatch) return htmlMatch[1].trim();
|
||||
return content;
|
||||
}
|
||||
|
||||
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 = '<div class="message-content user-message">' + processedContent + "</div>";
|
||||
} else {
|
||||
var cleanContent = stripMarkdownBlocks(content);
|
||||
var hasHtmlTags = /<\/?[a-zA-Z][^>]*>|<!--|-->/i.test(cleanContent);
|
||||
|
||||
var parsed;
|
||||
if (msgId) {
|
||||
parsed = '<div class="streaming-loading"><span class="loading-dots">...</span></div>';
|
||||
} else if (hasHtmlTags) {
|
||||
parsed = cleanContent;
|
||||
} else {
|
||||
parsed = typeof marked !== "undefined" && marked.parse
|
||||
? marked.parse(cleanContent)
|
||||
: escapeHtml(cleanContent);
|
||||
}
|
||||
parsed = renderMentionInMessage(parsed);
|
||||
div.innerHTML = '<div class="message-content bot-message">' + parsed + "</div>";
|
||||
}
|
||||
|
||||
messages.appendChild(div);
|
||||
|
||||
if (!ChatState.isUserScrolling) {
|
||||
scrollToBottom(true);
|
||||
} else {
|
||||
updateScrollButton();
|
||||
}
|
||||
|
||||
setupMentionClickHandlers(div);
|
||||
}
|
||||
|
||||
function isTagBalanced(html) {
|
||||
if (!html) return true;
|
||||
var lastChevronOpen = html.lastIndexOf('<');
|
||||
var lastChevronClose = html.lastIndexOf('>');
|
||||
if (lastChevronOpen > lastChevronClose) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function updateStreaming(content) {
|
||||
var el = document.getElementById(ChatState.streamingMessageId);
|
||||
if (!el) return;
|
||||
|
||||
var msgContent = el.querySelector(".message-content");
|
||||
var cleanContent = stripMarkdownBlocks(content);
|
||||
var isHtml = /<\/?[a-zA-Z][^>]*>|<!--|-->/i.test(cleanContent);
|
||||
|
||||
if (isHtml) {
|
||||
if (isTagBalanced(cleanContent) || (Date.now() - ChatState.lastRenderTime > 2000)) {
|
||||
msgContent.innerHTML = renderMentionInMessage(cleanContent);
|
||||
ChatState.lastRenderTime = Date.now();
|
||||
if (!ChatState.isUserScrolling) scrollToBottom(true);
|
||||
}
|
||||
} else {
|
||||
var parsed = typeof marked !== "undefined" && marked.parse
|
||||
? marked.parse(cleanContent)
|
||||
: escapeHtml(cleanContent);
|
||||
parsed = renderMentionInMessage(parsed);
|
||||
msgContent.innerHTML = parsed;
|
||||
if (!ChatState.isUserScrolling) scrollToBottom(true);
|
||||
}
|
||||
}
|
||||
|
||||
function finalizeStreaming() {
|
||||
var el = document.getElementById(ChatState.streamingMessageId);
|
||||
if (el) {
|
||||
var cleanContent = stripMarkdownBlocks(ChatState.currentStreamingContent);
|
||||
var hasHtmlTags = /<\/?[a-zA-Z][^>]*>|<!--|-->/i.test(cleanContent);
|
||||
var parsed = hasHtmlTags
|
||||
? cleanContent
|
||||
: (typeof marked !== "undefined" && marked.parse
|
||||
? marked.parse(cleanContent)
|
||||
: escapeHtml(cleanContent));
|
||||
parsed = renderMentionInMessage(parsed);
|
||||
el.querySelector(".message-content").innerHTML = parsed;
|
||||
el.removeAttribute("id");
|
||||
setupMentionClickHandlers(el);
|
||||
if (!ChatState.isUserScrolling) scrollToBottom(true);
|
||||
}
|
||||
ChatState.streamingMessageId = null;
|
||||
ChatState.currentStreamingContent = "";
|
||||
ChatState.streamingBuffer = "";
|
||||
}
|
||||
|
||||
function processMessage(data) {
|
||||
if (data.is_complete) {
|
||||
if (ChatState.isStreaming) {
|
||||
finalizeStreaming();
|
||||
} else if (data.content && data.content.trim() !== "") {
|
||||
addMessage("bot", data.content);
|
||||
}
|
||||
ChatState.isStreaming = false;
|
||||
if (data.suggestions && Array.isArray(data.suggestions) && data.suggestions.length > 0) {
|
||||
renderSuggestions(data.suggestions);
|
||||
}
|
||||
if (data.switchers && Array.isArray(data.switchers) && data.switchers.length > 0) {
|
||||
renderBotSwitchers(data.switchers);
|
||||
}
|
||||
} else {
|
||||
if (!ChatState.isStreaming) {
|
||||
ChatState.isStreaming = true;
|
||||
ChatState.streamingMessageId = "streaming-" + Date.now();
|
||||
ChatState.currentStreamingContent = data.content || "";
|
||||
addMessage("bot", ChatState.currentStreamingContent, ChatState.streamingMessageId);
|
||||
ChatState.lastRenderTime = Date.now();
|
||||
} else {
|
||||
ChatState.currentStreamingContent += data.content || "";
|
||||
var now = Date.now();
|
||||
if (now - ChatState.lastRenderTime > ChatState.renderInterval) {
|
||||
updateStreaming(ChatState.currentStreamingContent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
108
botui/ui/suite/chat/chat-state.js
Normal file
108
botui/ui/suite/chat/chat-state.js
Normal file
|
|
@ -0,0 +1,108 @@
|
|||
var ChatState = {
|
||||
ws: null,
|
||||
currentSessionId: null,
|
||||
currentUserId: null,
|
||||
currentBotId: "default",
|
||||
currentBotName: "default",
|
||||
isStreaming: false,
|
||||
streamingMessageId: null,
|
||||
currentStreamingContent: "",
|
||||
streamingBuffer: "",
|
||||
lastRenderTime: 0,
|
||||
renderInterval: 200,
|
||||
reconnectAttempts: 0,
|
||||
maxReconnectAttempts: 5,
|
||||
disconnectNotified: false,
|
||||
isUserScrolling: false,
|
||||
activeSwitchers: new Set(),
|
||||
switcherDefinitions: [],
|
||||
mentionState: {
|
||||
active: false,
|
||||
query: "",
|
||||
startPos: -1,
|
||||
selectedIndex: 0,
|
||||
results: [],
|
||||
},
|
||||
};
|
||||
|
||||
var WS_BASE_URL =
|
||||
window.location.protocol === "https:" ? "wss://" : "ws://";
|
||||
var WS_URL = WS_BASE_URL + window.location.host + "/ws";
|
||||
|
||||
var MessageType = {
|
||||
EXTERNAL: 0,
|
||||
USER: 1,
|
||||
BOT_RESPONSE: 2,
|
||||
CONTINUE: 3,
|
||||
SUGGESTION: 4,
|
||||
CONTEXT_CHANGE: 5,
|
||||
};
|
||||
|
||||
var EntityTypes = {
|
||||
lead: { icon: "\u{1F464}", color: "#4CAF50", label: "Lead", route: "crm" },
|
||||
opportunity: {
|
||||
icon: "\u{1F4B0}",
|
||||
color: "#FF9800",
|
||||
label: "Opportunity",
|
||||
route: "crm",
|
||||
},
|
||||
account: {
|
||||
icon: "\u{1F3E2}",
|
||||
color: "#2196F3",
|
||||
label: "Account",
|
||||
route: "crm",
|
||||
},
|
||||
contact: {
|
||||
icon: "\u{1F4C7}",
|
||||
color: "#9C27B0",
|
||||
label: "Contact",
|
||||
route: "crm",
|
||||
},
|
||||
invoice: {
|
||||
icon: "\u{1F4C4}",
|
||||
color: "#F44336",
|
||||
label: "Invoice",
|
||||
route: "billing",
|
||||
},
|
||||
quote: {
|
||||
icon: "\u{1F4CB}",
|
||||
color: "#607D8B",
|
||||
label: "Quote",
|
||||
route: "billing",
|
||||
},
|
||||
case: {
|
||||
icon: "\u{1F3AB}",
|
||||
color: "#E91E63",
|
||||
label: "Case",
|
||||
route: "tickets",
|
||||
},
|
||||
product: {
|
||||
icon: "\u{1F4E6}",
|
||||
color: "#795548",
|
||||
label: "Product",
|
||||
route: "products",
|
||||
},
|
||||
service: {
|
||||
icon: "\u2699\uFE0F",
|
||||
color: "#00BCD4",
|
||||
label: "Service",
|
||||
route: "products",
|
||||
},
|
||||
};
|
||||
|
||||
var SWITCHER_ICONS = {
|
||||
tables: "\u{1F4CA}",
|
||||
infographic: "\u{1F4CD}",
|
||||
cards: "\u{1F0CF}",
|
||||
list: "\u{1F4CB}",
|
||||
comparison: "\u2696",
|
||||
timeline: "\u23F0",
|
||||
markdown: "\u{1F4DD}",
|
||||
chart: "\u{1F4C8}",
|
||||
};
|
||||
|
||||
function escapeHtml(text) {
|
||||
var div = document.createElement("div");
|
||||
div.textContent = text;
|
||||
return div.innerHTML;
|
||||
}
|
||||
57
botui/ui/suite/chat/chat-suggestions.js
Normal file
57
botui/ui/suite/chat/chat-suggestions.js
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
function renderSuggestions(suggestions) {
|
||||
var suggestionsEl = document.getElementById("suggestions");
|
||||
if (!suggestionsEl) return;
|
||||
|
||||
suggestionsEl.innerHTML = "";
|
||||
suggestionsEl.classList.add("has-bot-suggestions");
|
||||
|
||||
suggestions.forEach(function (suggestion) {
|
||||
var chip = document.createElement("button");
|
||||
chip.className = "suggestion-chip";
|
||||
chip.textContent = suggestion.text || "Suggestion";
|
||||
|
||||
chip.onclick = (function (sugg) {
|
||||
return function () {
|
||||
if (sugg.action) {
|
||||
try {
|
||||
var action = typeof sugg.action === "string"
|
||||
? JSON.parse(sugg.action)
|
||||
: sugg.action;
|
||||
|
||||
if (action.type === "invoke_tool") {
|
||||
ChatState.ws.send(JSON.stringify({
|
||||
bot_id: ChatState.currentBotId,
|
||||
user_id: ChatState.currentUserId,
|
||||
session_id: ChatState.currentSessionId,
|
||||
channel: "web",
|
||||
content: action.tool,
|
||||
message_type: 6,
|
||||
active_switchers: Array.from(ChatState.activeSwitchers),
|
||||
timestamp: new Date().toISOString(),
|
||||
}));
|
||||
return;
|
||||
} else if (action.type === "switch_context" && action.switcher) {
|
||||
if (!ChatState.activeSwitchers.has(action.switcher)) {
|
||||
ChatState.activeSwitchers.add(action.switcher);
|
||||
renderSwitcherChips();
|
||||
}
|
||||
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) {
|
||||
window.sendMessage(sugg.text);
|
||||
}
|
||||
} else {
|
||||
window.sendMessage(sugg.text);
|
||||
}
|
||||
};
|
||||
})(suggestion);
|
||||
|
||||
suggestionsEl.appendChild(chip);
|
||||
});
|
||||
}
|
||||
|
|
@ -1,90 +1,55 @@
|
|||
// Chat Switchers Module - Manages format switchers (tables, infographic, cards, etc.)
|
||||
// Uses event delegation for better reliability with dynamic content
|
||||
|
||||
var activeSwitchers = new Set();
|
||||
var switcherDefinitions = [];
|
||||
|
||||
function renderBotSwitchers(switchers) {
|
||||
if (!switchers || switchers.length === 0) return;
|
||||
|
||||
var existingIds = {};
|
||||
switcherDefinitions.forEach(function(sw) { existingIds[sw.id] = true; });
|
||||
|
||||
switchers.forEach(function(sw) {
|
||||
if (!existingIds[sw.id]) {
|
||||
switcherDefinitions.push({
|
||||
id: sw.id,
|
||||
label: sw.label || sw.id,
|
||||
icon: sw.icon || '🔀',
|
||||
color: sw.color || '#666'
|
||||
});
|
||||
existingIds[sw.id] = true;
|
||||
}
|
||||
});
|
||||
|
||||
renderSwitchers();
|
||||
|
||||
var container = document.getElementById("switchers");
|
||||
if (container && switcherDefinitions.length > 0) {
|
||||
container.style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
function renderSwitchers() {
|
||||
var container = document.getElementById("switcherChips");
|
||||
if (!container) return;
|
||||
|
||||
container.innerHTML = switcherDefinitions.map(function(sw) {
|
||||
var isActive = activeSwitchers.has(sw.id);
|
||||
return (
|
||||
'<div class="switcher-chip' + (isActive ? ' active' : '') + '" ' +
|
||||
'data-switch-id="' + sw.id + '" ' +
|
||||
'style="--switcher-color: ' + sw.color + '; ' +
|
||||
(isActive ? 'color: ' + sw.color + ' background: ' + sw.color + '; ' : '') + '">' +
|
||||
'<span class="switcher-chip-icon">' + sw.icon + '</span>' +
|
||||
'<span>' + sw.label + '</span>' +
|
||||
'</div>'
|
||||
);
|
||||
}).join('');
|
||||
|
||||
// Event delegation - attach once to parent
|
||||
if (!container.dataset.hasClickHandler) {
|
||||
container.addEventListener('click', function(e) {
|
||||
var chip = e.target.closest('.switcher-chip');
|
||||
if (chip) {
|
||||
var switcherId = chip.getAttribute('data-switch-id');
|
||||
if (switcherId) {
|
||||
toggleSwitcher(switcherId);
|
||||
}
|
||||
}
|
||||
});
|
||||
container.dataset.hasClickHandler = 'true';
|
||||
}
|
||||
function renderSwitcherChips() {
|
||||
var container = document.getElementById("switcherChips");
|
||||
if (!container) return;
|
||||
container.innerHTML = ChatState.switcherDefinitions.map(function (sw) {
|
||||
var isActive = ChatState.activeSwitchers.has(sw.id);
|
||||
return '<div class="switcher-chip' + (isActive ? ' active' : '') + '" ' +
|
||||
'data-switch-id="' + sw.id + '" ' +
|
||||
'style="--switcher-color: ' + (sw.color || '#666') + '; ' +
|
||||
(isActive ? 'color: white; background: ' + (sw.color || '#666') + '; ' : '') + '">' +
|
||||
'<span class="switcher-chip-icon">' + (sw.icon || '\u{1F500}') + '</span>' +
|
||||
'<span>' + sw.label + '</span></div>';
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function toggleSwitcher(switcherId) {
|
||||
console.log('toggleSwitcher called with:', switcherId);
|
||||
console.log('activeSwitchers before:', Array.from(activeSwitchers));
|
||||
if (activeSwitchers.has(switcherId)) {
|
||||
activeSwitchers.delete(switcherId);
|
||||
console.log('Deleted switcher:', switcherId);
|
||||
} else {
|
||||
activeSwitchers.add(switcherId);
|
||||
console.log('Added switcher:', switcherId);
|
||||
if (ChatState.activeSwitchers.has(switcherId)) {
|
||||
ChatState.activeSwitchers.delete(switcherId);
|
||||
} else {
|
||||
ChatState.activeSwitchers.add(switcherId);
|
||||
}
|
||||
renderSwitcherChips();
|
||||
}
|
||||
|
||||
function renderBotSwitchers(switchers) {
|
||||
if (!switchers || switchers.length === 0) return;
|
||||
var existingIds = {};
|
||||
ChatState.switcherDefinitions.forEach(function (sw) { existingIds[sw.id] = true; });
|
||||
switchers.forEach(function (sw) {
|
||||
if (!existingIds[sw.id]) {
|
||||
ChatState.switcherDefinitions.push({
|
||||
id: sw.id,
|
||||
label: sw.label || sw.id,
|
||||
icon: sw.icon || SWITCHER_ICONS[sw.id] || "\u{1F500}",
|
||||
color: sw.color || "#666",
|
||||
});
|
||||
existingIds[sw.id] = true;
|
||||
}
|
||||
console.log('activeSwitchers after:', Array.from(activeSwitchers));
|
||||
|
||||
// Re-render to show active state
|
||||
renderSwitchers();
|
||||
}
|
||||
|
||||
function getActiveSwitcherIds() {
|
||||
var ids = Array.from(activeSwitchers);
|
||||
console.log('getActiveSwitcherIds returning:', ids);
|
||||
return ids;
|
||||
}
|
||||
|
||||
function clearSwitchers() {
|
||||
activeSwitchers.clear();
|
||||
renderSwitchers();
|
||||
});
|
||||
renderSwitcherChips();
|
||||
var container = document.getElementById("switchers");
|
||||
if (container && ChatState.switcherDefinitions.length > 0) {
|
||||
container.style.display = '';
|
||||
}
|
||||
var chipsContainer = document.getElementById("switcherChips");
|
||||
if (chipsContainer && !chipsContainer.dataset.hasClickHandler) {
|
||||
chipsContainer.addEventListener("click", function (e) {
|
||||
var chip = e.target.closest(".switcher-chip");
|
||||
if (chip) {
|
||||
var switcherId = chip.getAttribute("data-switch-id");
|
||||
if (switcherId) toggleSwitcher(switcherId);
|
||||
}
|
||||
});
|
||||
chipsContainer.dataset.hasClickHandler = "true";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
78
botui/ui/suite/chat/chat-theme.js
Normal file
78
botui/ui/suite/chat/chat-theme.js
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
function getContrastYIQ(hexcolor) {
|
||||
if (!hexcolor) return "#ffffff";
|
||||
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) {
|
||||
var color1 = themeData.color1 || themeData.data && themeData.data.color1 || "black";
|
||||
var color2 = themeData.color2 || themeData.data && themeData.data.color2 || "white";
|
||||
var logo = themeData.logo_url || themeData.data && themeData.data.logo_url || "";
|
||||
var title = themeData.title || themeData.data && themeData.data.title || window.__INITIAL_BOT_NAME__ || "Chat";
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
var botId = botName.toLowerCase();
|
||||
var botThemeKey = "gb-theme-" + botId;
|
||||
var localStorageTheme = localStorage.getItem(botThemeKey);
|
||||
var configThemeBase = config.theme_base || config["theme-base"] || "light";
|
||||
var useBotConfigColors = !localStorageTheme || localStorageTheme === "default" || localStorageTheme === configThemeBase;
|
||||
|
||||
var color1 = config.theme_color1 || config["theme-color1"] || config["Theme Color"] || "#3b82f6";
|
||||
var color2 = config.theme_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"] || "";
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
if (logo) {
|
||||
var logoImg = document.querySelector(".logo-icon-img");
|
||||
if (logoImg) { logoImg.src = logo; logoImg.alt = title || botName; logoImg.style.display = "block"; }
|
||||
var logoSvg = document.querySelector(".logo-icon-svg");
|
||||
if (logoSvg) logoSvg.style.display = "none";
|
||||
}
|
||||
})
|
||||
.catch(function () {});
|
||||
}
|
||||
106
botui/ui/suite/chat/chat-websocket.js
Normal file
106
botui/ui/suite/chat/chat-websocket.js
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
function notify(message, type) {
|
||||
type = type || "info";
|
||||
if (window.GBAlerts) {
|
||||
if (type === "error") {
|
||||
window.GBAlerts.warning("Chat", message);
|
||||
} else {
|
||||
window.GBAlerts.info("Chat", message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 connectWebSocket() {
|
||||
if (ChatState.ws) ChatState.ws.close();
|
||||
updateConnectionStatus("connecting");
|
||||
|
||||
var url = WS_URL +
|
||||
"?session_id=" + ChatState.currentSessionId +
|
||||
"&user_id=" + ChatState.currentUserId +
|
||||
"&bot_name=" + ChatState.currentBotName;
|
||||
|
||||
ChatState.ws = new WebSocket(url);
|
||||
|
||||
ChatState.ws.onopen = function () {
|
||||
ChatState.disconnectNotified = false;
|
||||
updateConnectionStatus("connected");
|
||||
var loadingOverlay = document.getElementById("chatLoadingOverlay");
|
||||
var contentWrapper = document.getElementById("chatContentWrapper");
|
||||
if (loadingOverlay) loadingOverlay.style.display = "none";
|
||||
if (contentWrapper) contentWrapper.style.display = "flex";
|
||||
};
|
||||
|
||||
ChatState.ws.onmessage = function (event) {
|
||||
try {
|
||||
var data = JSON.parse(event.data);
|
||||
|
||||
if (data.type === "connected") {
|
||||
ChatState.reconnectAttempts = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.event) {
|
||||
if (data.event === "change_theme") applyThemeData(data.data || {});
|
||||
return;
|
||||
}
|
||||
|
||||
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) {}
|
||||
}
|
||||
|
||||
if (window.AgentMode && data.type &&
|
||||
["thought_process", "terminal_output", "browser_ready", "step_progress", "step_complete", "todo_update", "agent_status", "file_created"].indexOf(data.type) !== -1) {
|
||||
window.AgentMode.handleMessage(data);
|
||||
}
|
||||
|
||||
if (data.message_type === MessageType.BOT_RESPONSE) {
|
||||
processMessage(data);
|
||||
}
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
ChatState.ws.onclose = function () {
|
||||
updateConnectionStatus("disconnected");
|
||||
if (!ChatState.disconnectNotified) {
|
||||
notify("Disconnected from chat server", "error");
|
||||
ChatState.disconnectNotified = true;
|
||||
}
|
||||
if (ChatState.reconnectAttempts < ChatState.maxReconnectAttempts) {
|
||||
ChatState.reconnectAttempts++;
|
||||
updateConnectionStatus("connecting");
|
||||
setTimeout(connectWebSocket, 1000 * ChatState.reconnectAttempts);
|
||||
}
|
||||
};
|
||||
|
||||
ChatState.ws.onerror = function () {
|
||||
updateConnectionStatus("disconnected");
|
||||
};
|
||||
}
|
||||
|
|
@ -1,59 +1,70 @@
|
|||
<link rel="stylesheet" href="/suite/chat/chat.css?v=10" />
|
||||
<link rel="stylesheet" href="/suite/css/markdown-message.css" />
|
||||
<script src="/suite/js/vendor/marked.min.js"></script>
|
||||
<link rel="stylesheet" href="/suite/css/chat-agent-mode.css" />
|
||||
|
||||
<div class="chat-layout" id="chat-app">
|
||||
<div class="connection-status connecting" id="connectionStatus" style="display: none">
|
||||
<span class="connection-status-dot"></span>
|
||||
<span class="connection-text">Connecting...</span>
|
||||
<div class="chat-loading-overlay" id="chatLoadingOverlay">
|
||||
<div class="chat-loading-spinner"></div>
|
||||
<div class="chat-loading-text">Carregando...</div>
|
||||
</div>
|
||||
|
||||
<main id="messages"></main>
|
||||
|
||||
<footer>
|
||||
<div class="chat-footer-content">
|
||||
<div class="suggestions-container" id="suggestions"></div>
|
||||
<div class="switchers-container" id="switchers" style="display:none">
|
||||
<div class="switchers-label">Formato:</div>
|
||||
<div class="switchers-chips" id="switchersChips"></div>
|
||||
</div>
|
||||
<div class="chat-content-wrapper" id="chatContentWrapper" style="display: none;">
|
||||
<div class="connection-status connecting" id="connectionStatus" style="display: none">
|
||||
<span class="connection-status-dot"></span>
|
||||
<span class="connection-text">Connecting...</span>
|
||||
</div>
|
||||
<div class="mention-dropdown" id="mentionDropdown">
|
||||
<div class="mention-header">
|
||||
<span class="mention-title" data-i18n="chat-mention-title">Reference Entity</span>
|
||||
|
||||
<main id="messages"></main>
|
||||
|
||||
<footer>
|
||||
<div class="chat-footer-content">
|
||||
<div class="suggestions-container" id="suggestions"></div>
|
||||
<div class="switchers-container" id="switchers" style="display:none">
|
||||
<div class="switchers-label">Formato:</div>
|
||||
<div class="switchers-chips" id="switcherChips"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mention-results" id="mentionResults"></div>
|
||||
</div>
|
||||
<form class="input-container" id="chatForm">
|
||||
<input name="content" id="messageInput" type="text"
|
||||
placeholder="Message... (type @ to mention)"
|
||||
data-i18n-placeholder="chat-placeholder"
|
||||
autofocus autocomplete="off" />
|
||||
<button type="submit" id="sendBtn" title="Send" data-i18n-title="chat-send">↑</button>
|
||||
</form>
|
||||
</footer>
|
||||
|
||||
<button class="scroll-to-bottom" id="scrollToBottom" title="Scroll to bottom">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="entity-card-tooltip" id="entityCardTooltip">
|
||||
<div class="entity-card-header">
|
||||
<span class="entity-card-type"></span>
|
||||
<span class="entity-card-status"></span>
|
||||
<div class="mention-dropdown" id="mentionDropdown">
|
||||
<div class="mention-header">
|
||||
<span class="mention-title" data-i18n="chat-mention-title">Reference Entity</span>
|
||||
</div>
|
||||
<div class="mention-results" id="mentionResults"></div>
|
||||
</div>
|
||||
<form class="input-container" id="chatForm">
|
||||
<input name="content" id="messageInput" type="text"
|
||||
placeholder="Message... (type @ to mention)"
|
||||
data-i18n-placeholder="chat-placeholder" autofocus autocomplete="off" />
|
||||
<button type="submit" id="sendBtn" title="Send" data-i18n-title="chat-send">↑</button>
|
||||
</form>
|
||||
</footer>
|
||||
<button class="scroll-to-bottom" id="scrollToBottom" title="Scroll to bottom">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="6 9 12 15 18 9"></polyline>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="entity-card-title"></div>
|
||||
<div class="entity-card-details"></div>
|
||||
<div class="entity-card-actions">
|
||||
<button class="entity-card-btn" data-action="view" data-i18n="action-view">View</button>
|
||||
|
||||
<div class="entity-card-tooltip" id="entityCardTooltip">
|
||||
<div class="entity-card-header">
|
||||
<span class="entity-card-type"></span>
|
||||
<span class="entity-card-status"></span>
|
||||
</div>
|
||||
<div class="entity-card-title"></div>
|
||||
<div class="entity-card-details"></div>
|
||||
<div class="entity-card-actions">
|
||||
<button class="entity-card-btn" data-action="view" data-i18n="action-view">View</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Chat Modules -->
|
||||
<script src="/suite/js/vendor/marked.min.js"></script>
|
||||
<script src="/suite/chat/chat-state.js"></script>
|
||||
<script src="/suite/chat/chat-switchers.js"></script>
|
||||
<script src="/suite/chat/chat-mentions.js"></script>
|
||||
<script src="/suite/chat/chat-messages.js"></script>
|
||||
<script src="/suite/chat/chat-main.js"></script>
|
||||
<script src="/suite/chat/chat-suggestions.js"></script>
|
||||
<script src="/suite/chat/chat-theme.js"></script>
|
||||
<script src="/suite/chat/chat-websocket.js"></script>
|
||||
<script src="/suite/chat/chat-init.js"></script>
|
||||
<script src="/suite/js/chat-agent-mode.js"></script>
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
101
botui/ui/suite/vibe/vibe-agents.js
Normal file
101
botui/ui/suite/vibe/vibe-agents.js
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
function updateMantis1(status, detail) {
|
||||
var card = document.querySelector(
|
||||
'.as-agent-card[data-agent-id="1"]',
|
||||
);
|
||||
if (!card) return;
|
||||
var bar = card.querySelector(".as-agent-bar .as-bar-fill");
|
||||
if (status === "working") {
|
||||
card.style.borderLeftColor = "#f59e0b";
|
||||
if (!card.querySelector(".as-agent-bar")) {
|
||||
var barWrapper = document.createElement("div");
|
||||
barWrapper.className = "as-agent-bar";
|
||||
barWrapper.innerHTML =
|
||||
'<div class="as-bar-fill bred" style="width:0%;transition:width 0.5s;"></div>';
|
||||
card.appendChild(barWrapper);
|
||||
}
|
||||
} else if (status === "done") {
|
||||
card.style.borderLeftColor = "var(--accent)";
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
function updateAgentCard(agentId, status, detail) {
|
||||
var card = document.querySelector(
|
||||
'.as-agent-card[data-agent-id="' + agentId + '"]',
|
||||
);
|
||||
if (!card) return;
|
||||
card.style.opacity = "1";
|
||||
|
||||
var badge = card.querySelector(".as-badge");
|
||||
var dot = card.querySelector(".as-status-dot");
|
||||
|
||||
if (status === "WORKING") {
|
||||
card.style.borderLeft = "3px solid #f59e0b";
|
||||
if (dot) {
|
||||
dot.className = "as-status-dot yellow";
|
||||
}
|
||||
if (badge) {
|
||||
badge.textContent = "WORKING";
|
||||
badge.className = "as-badge badge-bred";
|
||||
}
|
||||
if (!card.querySelector(".as-agent-bar")) {
|
||||
var barWrapper = document.createElement("div");
|
||||
barWrapper.className = "as-agent-bar";
|
||||
barWrapper.innerHTML =
|
||||
'<div class="as-bar-fill bred" style="width:0%;transition:width 0.5s;"></div>';
|
||||
card.appendChild(barWrapper);
|
||||
}
|
||||
} else if (status === "EVOLVED" || status === "DONE") {
|
||||
card.style.borderLeft = "3px solid var(--accent)";
|
||||
if (dot) {
|
||||
dot.className = "as-status-dot green";
|
||||
}
|
||||
if (badge) {
|
||||
badge.textContent = "EVOLVED";
|
||||
badge.className = "as-badge badge-evolved";
|
||||
}
|
||||
var agBar = card.querySelector(".as-bar-fill");
|
||||
if (agBar) agBar.style.width = "100%";
|
||||
setTimeout(function () {
|
||||
var b = card.querySelector(".as-agent-bar");
|
||||
if (b) b.remove();
|
||||
}, 2000);
|
||||
} else if (status === "BRED") {
|
||||
card.style.borderLeft = "3px solid #f59e0b";
|
||||
if (dot) {
|
||||
dot.className = "as-status-dot yellow";
|
||||
}
|
||||
if (badge) {
|
||||
badge.textContent = "BRED";
|
||||
badge.className = "as-badge badge-bred";
|
||||
}
|
||||
} else if (status === "FAILED") {
|
||||
card.style.borderLeft = "3px solid #ef4444";
|
||||
if (dot) {
|
||||
dot.className = "as-status-dot red";
|
||||
}
|
||||
if (badge) {
|
||||
badge.textContent = "FAILED";
|
||||
badge.className = "as-badge badge-bred";
|
||||
badge.style.background = "#ef4444";
|
||||
}
|
||||
}
|
||||
|
||||
if (detail) {
|
||||
var detailEl = card.querySelector(".as-agent-detail");
|
||||
if (!detailEl) {
|
||||
detailEl = document.createElement("span");
|
||||
detailEl.className = "as-agent-detail";
|
||||
detailEl.style.cssText =
|
||||
"font-size:10px;color: var(--text-muted);display:block;padding:0 12px 4px;";
|
||||
var body = card.querySelector(".as-agent-body");
|
||||
if (body) body.after(detailEl);
|
||||
}
|
||||
detailEl.textContent = detail;
|
||||
}
|
||||
}
|
||||
144
botui/ui/suite/vibe/vibe-autotask.js
Normal file
144
botui/ui/suite/vibe/vibe-autotask.js
Normal file
|
|
@ -0,0 +1,144 @@
|
|||
function callAutotask(intent) {
|
||||
updateMantis1("working");
|
||||
vibeAddMsg("system", "🔄 Mantis #1 is analyzing your request…");
|
||||
|
||||
connectTaskProgressWs(null);
|
||||
|
||||
var breadcrumb = document.querySelector(
|
||||
".vibe-canvas div:first-child",
|
||||
);
|
||||
if (breadcrumb) {
|
||||
currentProject = intent
|
||||
.substring(0, 40)
|
||||
.replace(/[^a-zA-Z0-9 ]/g, "");
|
||||
breadcrumb.innerHTML =
|
||||
'// DASHBOARD <span style="color: var(--text-secondary);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>';
|
||||
}
|
||||
|
||||
var token =
|
||||
localStorage.getItem("gb-access-token") ||
|
||||
sessionStorage.getItem("gb-access-token");
|
||||
fetch("/api/autotask/classify", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: "Bearer " + token,
|
||||
},
|
||||
body: JSON.stringify({ intent: intent, auto_process: true }),
|
||||
})
|
||||
.then(function (r) {
|
||||
return r.json();
|
||||
})
|
||||
.then(function (data) {
|
||||
updateMantis1("done");
|
||||
|
||||
if (data.success && data.result) {
|
||||
var r = data.result;
|
||||
|
||||
if (r.task_id) {
|
||||
connectTaskProgressWs(r.task_id);
|
||||
}
|
||||
|
||||
if (
|
||||
r.created_resources &&
|
||||
r.created_resources.length > 0
|
||||
) {
|
||||
r.created_resources.forEach(function (res, i) {
|
||||
setTimeout(function () {
|
||||
addTaskNode(
|
||||
res.name || res.resource_type,
|
||||
res.resource_type +
|
||||
(res.path ? " → " + res.path : ""),
|
||||
{ status: "Done" },
|
||||
);
|
||||
}, i * 400);
|
||||
});
|
||||
} else {
|
||||
addTaskNode(
|
||||
"Project Setup",
|
||||
"Setting up: " + intent,
|
||||
{ status: "Planning" },
|
||||
);
|
||||
}
|
||||
|
||||
vibeAddMsg(
|
||||
"bot",
|
||||
r.message || "Done! Your project is ready.",
|
||||
);
|
||||
|
||||
if (r.app_url) {
|
||||
vibeAddMsg(
|
||||
"system",
|
||||
'✅ App available at <a href="' +
|
||||
r.app_url +
|
||||
'" target="_blank" style="color: var(--accent);text-decoration:underline;">' +
|
||||
esc(r.app_url) +
|
||||
"</a>",
|
||||
);
|
||||
|
||||
var preview =
|
||||
document.getElementById("vibePreview");
|
||||
var urlBar =
|
||||
document.getElementById("vibePreviewUrl");
|
||||
var content =
|
||||
document.getElementById("vibePreviewContent");
|
||||
if (preview) preview.style.display = "";
|
||||
if (urlBar) urlBar.value = r.app_url;
|
||||
if (content)
|
||||
content.innerHTML =
|
||||
'<iframe src="' +
|
||||
r.app_url +
|
||||
'" style="width:100%;height:100%;border:none;"></iframe>';
|
||||
}
|
||||
|
||||
if (r.next_steps && r.next_steps.length > 0) {
|
||||
vibeAddMsg(
|
||||
"bot",
|
||||
"**Next steps:**\n" +
|
||||
r.next_steps
|
||||
.map(function (s) { return "• " + s; })
|
||||
.join("\n"),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
vibeAddMsg(
|
||||
"bot",
|
||||
"I classified your intent as **" +
|
||||
(data.intent_type || "UNKNOWN") +
|
||||
"**. " +
|
||||
(data.error || "Processing complete."),
|
||||
);
|
||||
addTaskNode("Analysis", intent, { status: "Planning" });
|
||||
}
|
||||
})
|
||||
.catch(function (err) {
|
||||
updateMantis1("done");
|
||||
vibeAddMsg(
|
||||
"system",
|
||||
"⚠️ Backend unavailable — showing plan preview.",
|
||||
);
|
||||
var words = intent.split(/[.,;]/);
|
||||
addTaskNode(
|
||||
"Project Setup",
|
||||
"Create project structure and install dependencies",
|
||||
{ status: "Planning" },
|
||||
);
|
||||
if (words.length > 1) {
|
||||
setTimeout(function () {
|
||||
addTaskNode(
|
||||
"Database Schema",
|
||||
"Define tables for: " + words.slice(0, 3).join(", "),
|
||||
{ status: "Pending" },
|
||||
);
|
||||
}, 500);
|
||||
}
|
||||
vibeAddMsg(
|
||||
"bot",
|
||||
"I've created a preliminary plan with " +
|
||||
Math.min(words.length + 1, 5) +
|
||||
" nodes. Once the backend is available, I'll process the full build.",
|
||||
);
|
||||
});
|
||||
}
|
||||
131
botui/ui/suite/vibe/vibe-canvas.js
Normal file
131
botui/ui/suite/vibe/vibe-canvas.js
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
function addTaskNode(title, description, meta) {
|
||||
var stepsContainer = document.getElementById("vibeSteps");
|
||||
if (!stepsContainer) return;
|
||||
stepsContainer.style.display = "flex";
|
||||
var emptyState = document.getElementById("vibeCanvasEmpty");
|
||||
if (emptyState) emptyState.style.display = "none";
|
||||
|
||||
nodeIdCounter++;
|
||||
meta = meta || {};
|
||||
var fileCount =
|
||||
meta.estimated_files ||
|
||||
meta.files ||
|
||||
Math.floor(Math.random() * 15 + 3);
|
||||
var time =
|
||||
meta.estimated_time ||
|
||||
meta.time ||
|
||||
Math.floor(Math.random() * 20 + 5) + "m";
|
||||
var tokens =
|
||||
meta.estimated_tokens ||
|
||||
meta.tokens ||
|
||||
"~" + Math.floor(Math.random() * 30 + 10) + "k tokens";
|
||||
var status = meta.status || "Planning";
|
||||
var fileList = meta.fileList || [];
|
||||
var isFirst = stepsContainer.children.length === 0;
|
||||
var nodeId = "vibe-node-" + nodeIdCounter;
|
||||
|
||||
var statusBg =
|
||||
status === "Done"
|
||||
? "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)";
|
||||
|
||||
var subTasksHtml = "";
|
||||
if (fileList.length > 0) {
|
||||
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);">';
|
||||
for (var fi = 0; fi < fileList.length; fi++) {
|
||||
subTasksHtml +=
|
||||
'<div style="padding:2px 0;display:flex;align-items:center;gap:4px;"><span style="color: var(--accent);">📄</span> ' +
|
||||
esc(fileList[fi]) +
|
||||
"</div>";
|
||||
}
|
||||
subTasksHtml += "</div>";
|
||||
}
|
||||
|
||||
var node = document.createElement("div");
|
||||
node.className = "vibe-task-node";
|
||||
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;";
|
||||
|
||||
node.innerHTML =
|
||||
'<div style="padding:12px 16px;border-bottom: 1px solid var(--border);">' +
|
||||
'<div style="display:flex;justify-content:space-between;margin-bottom:8px;font-size:10px;color: var(--text-muted);">' +
|
||||
"<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>" +
|
||||
'<div style="padding:10px 16px;background: var(--surface);border-bottom: 1px solid var(--border);font-size:11px;">' +
|
||||
'<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:8px;">' +
|
||||
'<span style="color: var(--text-muted);">Status</span>' +
|
||||
'<span style="background:' +
|
||||
statusBg +
|
||||
";color:" +
|
||||
statusColor +
|
||||
';padding:2px 8px;border-radius:12px;font-weight:600;">' +
|
||||
esc(status) +
|
||||
"</span>" +
|
||||
"</div>" +
|
||||
'<div style="display:flex;justify-content:space-between;align-items:center;">' +
|
||||
'<span style="color: var(--text-muted);">Mantis Manager</span>' +
|
||||
'<span style="display:flex;align-items:center;gap:4px;"><span class="as-status-dot green"></span> Mantis #1</span>' +
|
||||
"</div>" +
|
||||
"</div>" +
|
||||
'<div style="padding:8px 16px;font-size:10px;font-weight:700;color: var(--text-muted);">' +
|
||||
'<div data-toggle="' +
|
||||
nodeId +
|
||||
"-files\" style=\"padding:4px 0;cursor:pointer;user-select:none;\" onclick=\"(function(el){var t=document.getElementById(el.getAttribute('data-toggle'));if(t){t.style.display=t.style.display==='none'?'':'none';var a=el.querySelector('span');if(a)a.textContent=t.style.display==='none'?'▶':'▼';}})(this)\">// SUB-TASKS <span style=\"float:right;\">▶</span></div>" +
|
||||
'<div style="padding:4px 0;cursor:pointer;">// LOGS <span style="float:right;">▶</span></div>' +
|
||||
"</div>" +
|
||||
subTasksHtml;
|
||||
|
||||
if (isFirst || stepsContainer.children.length > 0) {
|
||||
var line = document.createElement("div");
|
||||
line.style.cssText =
|
||||
"position:absolute;right:-60px;top:50%;width:60px;height:2px;background:var(--accent);z-index:10;";
|
||||
node.appendChild(line);
|
||||
if (!isFirst) {
|
||||
var dot = document.createElement("div");
|
||||
dot.style.cssText =
|
||||
"position:absolute;left:-5px;top:50%;transform:translateY(-50%);width:10px;height:10px;border-radius:50%;background:var(--accent);z-index:20;";
|
||||
node.appendChild(dot);
|
||||
}
|
||||
}
|
||||
|
||||
stepsContainer.appendChild(node);
|
||||
stepsContainer.scrollLeft = stepsContainer.scrollWidth;
|
||||
taskNodes.push({
|
||||
title: title,
|
||||
description: description,
|
||||
meta: meta,
|
||||
});
|
||||
return node;
|
||||
}
|
||||
122
botui/ui/suite/vibe/vibe-deploy.js
Normal file
122
botui/ui/suite/vibe/vibe-deploy.js
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
function selectDeploymentTarget(target) {
|
||||
selectedDeploymentTarget = target;
|
||||
|
||||
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";
|
||||
}
|
||||
}
|
||||
|
||||
async function executeDeployment() {
|
||||
var deployButton = document.getElementById("deployButton");
|
||||
if (deployButton) {
|
||||
deployButton.textContent = "Deploying...";
|
||||
deployButton.disabled = true;
|
||||
}
|
||||
|
||||
let payload = {
|
||||
app_name: document.getElementById('deployRepoName')?.value || document.getElementById('deployRoute')?.value || "my-app",
|
||||
target: {},
|
||||
environment: "production",
|
||||
manifest: {}
|
||||
};
|
||||
|
||||
if (selectedDeploymentTarget === 'external') {
|
||||
payload.target = {
|
||||
External: {
|
||||
repo_url: "https://alm.pragmatismo.com.br/" + payload.app_name,
|
||||
custom_domain: document.getElementById('deployCustomDomain')?.value || null,
|
||||
ci_cd_enabled: document.getElementById('deployCiCd')?.checked ?? true
|
||||
}
|
||||
};
|
||||
payload.app_type = document.getElementById('deployAppType')?.value || "htmx";
|
||||
} else {
|
||||
let route = document.getElementById('deployRoute')?.value || "my-app";
|
||||
payload.target = {
|
||||
Internal: {
|
||||
route: "/apps/" + route,
|
||||
shared_resources: document.getElementById('deploySharedResources')?.checked ?? true
|
||||
}
|
||||
};
|
||||
payload.app_type = "gb-native";
|
||||
}
|
||||
|
||||
try {
|
||||
vibeAddMsg("system", "🚀 Initiating deployment API call...");
|
||||
const response = await fetch('/api/deployment/deploy', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && (result.success || result.status === 'Deployed' || result.status === 'Building')) {
|
||||
closeDeploymentModal();
|
||||
vibeAddMsg("system", "✅ Deployment Successful! " + (result.url ? result.url : ""));
|
||||
|
||||
var previewUrl = document.getElementById("vibePreviewUrl");
|
||||
var previewPanel = document.getElementById("vibePreview");
|
||||
if (previewUrl && previewPanel && result.url) {
|
||||
previewUrl.value = result.url;
|
||||
previewPanel.style.display = "block";
|
||||
}
|
||||
} else {
|
||||
vibeAddMsg("system", "❌ Deployment failed: " + (result.error || result.status));
|
||||
}
|
||||
} catch (e) {
|
||||
vibeAddMsg("system", "❌ Deployment error: " + e.message);
|
||||
} finally {
|
||||
if (deployButton) {
|
||||
deployButton.textContent = "Deploy Now";
|
||||
deployButton.disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
104
botui/ui/suite/vibe/vibe-helpers.js
Normal file
104
botui/ui/suite/vibe/vibe-helpers.js
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
function esc(text) {
|
||||
var d = document.createElement("div");
|
||||
d.textContent = text || "";
|
||||
return d.innerHTML;
|
||||
}
|
||||
|
||||
function vibeAddMsg(role, text) {
|
||||
var box = document.getElementById("vibeChatMessages");
|
||||
if (!box) return;
|
||||
var div = document.createElement("div");
|
||||
if (role === "user") {
|
||||
div.style.cssText =
|
||||
"align-self:flex-end;background:var(--accent);color:var(--surface);font-weight:500;padding:10px 14px;border-radius:12px 12px 0 12px;max-width:85%;word-wrap:break-word;";
|
||||
div.textContent = text;
|
||||
} else if (role === "system") {
|
||||
div.style.cssText =
|
||||
"align-self:center;background:rgba(132,214,105,0.12);color: var(--accent);padding:6px 12px;border-radius:8px;font-size:11px;text-align:center;";
|
||||
div.innerHTML = text;
|
||||
} else {
|
||||
div.style.cssText =
|
||||
"align-self:flex-start;background:var(--surface-hover);color:var(--text);padding:10px 14px;border-radius:12px 12px 12px 0;max-width:85%;word-wrap:break-word;";
|
||||
div.className = "vibe-bot-msg";
|
||||
if (typeof marked !== "undefined" && marked.parse) {
|
||||
div.innerHTML = marked.parse(text);
|
||||
} else {
|
||||
div.textContent = text;
|
||||
}
|
||||
}
|
||||
box.appendChild(div);
|
||||
box.scrollTop = box.scrollHeight;
|
||||
return div;
|
||||
}
|
||||
|
||||
function vibeAddStreamStart() {
|
||||
vibeStreamId = "vibe-stream-" + Date.now();
|
||||
vibeStreamContent = "";
|
||||
var el = vibeAddMsg("bot", "▍");
|
||||
if (el) el.id = vibeStreamId;
|
||||
return el;
|
||||
}
|
||||
|
||||
function vibeUpdateStream(content) {
|
||||
vibeStreamContent += content || "";
|
||||
var el = document.getElementById(vibeStreamId);
|
||||
if (!el) return;
|
||||
if (typeof marked !== "undefined" && marked.parse) {
|
||||
el.innerHTML = marked.parse(vibeStreamContent);
|
||||
} else {
|
||||
el.textContent = vibeStreamContent;
|
||||
}
|
||||
var box = document.getElementById("vibeChatMessages");
|
||||
if (box) box.scrollTop = box.scrollHeight;
|
||||
}
|
||||
|
||||
function vibeFinalizeStream() {
|
||||
var el = document.getElementById(vibeStreamId);
|
||||
if (el) {
|
||||
if (typeof marked !== "undefined" && marked.parse) {
|
||||
el.innerHTML = marked.parse(vibeStreamContent);
|
||||
} else {
|
||||
el.textContent = vibeStreamContent;
|
||||
}
|
||||
el.removeAttribute("id");
|
||||
}
|
||||
vibeStreamId = null;
|
||||
vibeStreamContent = "";
|
||||
vibeStreaming = false;
|
||||
}
|
||||
|
||||
function setVibeStatus(status) {
|
||||
var dot = document.getElementById("vibeChatStatusDot");
|
||||
var badge = document.getElementById("vibeChatStatusBadge");
|
||||
if (status === "connected") {
|
||||
if (dot) {
|
||||
dot.className = "as-status-dot green";
|
||||
dot.style.boxShadow = "0 0 8px var(--accent)";
|
||||
}
|
||||
if (badge) {
|
||||
badge.textContent = "EVOLVED";
|
||||
badge.style.background = "var(--accent)";
|
||||
badge.style.color = "var(--bg)";
|
||||
}
|
||||
} else if (status === "connecting") {
|
||||
if (dot) {
|
||||
dot.className = "as-status-dot yellow";
|
||||
dot.style.boxShadow = "0 0 8px #f59e0b";
|
||||
}
|
||||
if (badge) {
|
||||
badge.textContent = "CONNECTING…";
|
||||
badge.style.background = "var(--surface-hover)";
|
||||
badge.style.color = "var(--text-muted)";
|
||||
}
|
||||
} else {
|
||||
if (dot) {
|
||||
dot.className = "as-status-dot red";
|
||||
dot.style.boxShadow = "0 0 8px #ef4444";
|
||||
}
|
||||
if (badge) {
|
||||
badge.textContent = "OFFLINE";
|
||||
badge.style.background = "var(--surface-hover)";
|
||||
badge.style.color = "var(--text-muted)";
|
||||
}
|
||||
}
|
||||
}
|
||||
71
botui/ui/suite/vibe/vibe-init.js
Normal file
71
botui/ui/suite/vibe/vibe-init.js
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
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);
|
||||
|
||||
callAutotask(text);
|
||||
}
|
||||
|
||||
function setupPipelineTabs() {
|
||||
var container = document.querySelector(".vibe-pipeline");
|
||||
if (!container) return;
|
||||
container.addEventListener("click", function (e) {
|
||||
var tab = e.target.closest(".vibe-pipeline-tab");
|
||||
if (!tab) return;
|
||||
container
|
||||
.querySelectorAll(".vibe-pipeline-tab")
|
||||
.forEach(function (t) {
|
||||
t.classList.remove("active");
|
||||
});
|
||||
tab.classList.add("active");
|
||||
});
|
||||
}
|
||||
|
||||
function setupSidebarCollapse() {
|
||||
var btn = document.getElementById("agentsSidebarCollapse");
|
||||
var sidebar = document.getElementById("agentsSidebar");
|
||||
if (!btn || !sidebar) return;
|
||||
btn.addEventListener("click", function () {
|
||||
sidebar.classList.toggle("collapsed");
|
||||
btn.textContent = sidebar.classList.contains("collapsed")
|
||||
? "▶"
|
||||
: "◀";
|
||||
});
|
||||
}
|
||||
|
||||
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 ? "▶" : "▼";
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function initVibe() {
|
||||
setupPipelineTabs();
|
||||
setupSidebarCollapse();
|
||||
setupWorkspaceAccordions();
|
||||
|
||||
var form = document.getElementById("vibeChatForm");
|
||||
if (form) form.addEventListener("submit", handleVibeSubmit);
|
||||
|
||||
connectVibeWs();
|
||||
}
|
||||
|
||||
if (document.readyState === "loading") {
|
||||
document.addEventListener("DOMContentLoaded", initVibe);
|
||||
} else {
|
||||
initVibe();
|
||||
}
|
||||
13
botui/ui/suite/vibe/vibe-state.js
Normal file
13
botui/ui/suite/vibe/vibe-state.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
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;
|
||||
var taskProgressWs = null;
|
||||
var selectedDeploymentTarget = "internal";
|
||||
271
botui/ui/suite/vibe/vibe-websocket.js
Normal file
271
botui/ui/suite/vibe/vibe-websocket.js
Normal file
|
|
@ -0,0 +1,271 @@
|
|||
function connectVibeWs() {
|
||||
setVibeStatus("connecting");
|
||||
|
||||
var botName = window.__INITIAL_BOT_NAME__ || "default";
|
||||
fetch("/api/auth?bot_name=" + encodeURIComponent(botName))
|
||||
.then(function (r) {
|
||||
return r.json();
|
||||
})
|
||||
.then(function (auth) {
|
||||
vibeUserId = auth.user_id;
|
||||
vibeSessionId = auth.session_id;
|
||||
vibeBotId = auth.bot_id || "default";
|
||||
vibeBotName = botName;
|
||||
|
||||
var proto =
|
||||
location.protocol === "https:" ? "wss://" : "ws://";
|
||||
var url =
|
||||
proto +
|
||||
location.host +
|
||||
"/ws?session_id=" +
|
||||
vibeSessionId +
|
||||
"&user_id=" +
|
||||
vibeUserId +
|
||||
"&bot_name=" +
|
||||
vibeBotName;
|
||||
vibeWs = new WebSocket(url);
|
||||
|
||||
vibeWs.onopen = function () {
|
||||
setVibeStatus("connected");
|
||||
};
|
||||
|
||||
vibeWs.onmessage = function (event) {
|
||||
try {
|
||||
var data = JSON.parse(event.data);
|
||||
if (data.type === "connected") return;
|
||||
if (data.event) return;
|
||||
|
||||
if (data.type === "thought_process") {
|
||||
vibeAddMsg("system", "💭 " + esc(data.content));
|
||||
return;
|
||||
}
|
||||
if (data.type === "terminal_output") {
|
||||
vibeAddMsg("system", "🖥️ " + esc(data.line));
|
||||
return;
|
||||
}
|
||||
if (data.type === "step_progress") {
|
||||
var pct = Math.round(
|
||||
(data.current / data.total) * 100,
|
||||
);
|
||||
updateMantis1("working");
|
||||
var bar = document.querySelector(
|
||||
'.as-agent-card[data-agent-id="1"] .as-bar-fill',
|
||||
);
|
||||
if (bar) bar.style.width = pct + "%";
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.message_type === 2) {
|
||||
if (data.is_complete) {
|
||||
if (vibeStreaming) {
|
||||
vibeFinalizeStream();
|
||||
} else if (
|
||||
data.content &&
|
||||
data.content.trim()
|
||||
) {
|
||||
vibeAddMsg("bot", data.content);
|
||||
}
|
||||
vibeStreaming = false;
|
||||
} else {
|
||||
if (!vibeStreaming) {
|
||||
vibeStreaming = true;
|
||||
vibeAddStreamStart();
|
||||
vibeUpdateStream(data.content || "");
|
||||
} else {
|
||||
vibeUpdateStream(data.content || "");
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Vibe WS parse error:", e);
|
||||
}
|
||||
};
|
||||
|
||||
vibeWs.onclose = function () {
|
||||
setVibeStatus("disconnected");
|
||||
};
|
||||
vibeWs.onerror = function () {
|
||||
setVibeStatus("disconnected");
|
||||
};
|
||||
})
|
||||
.catch(function () {
|
||||
setVibeStatus("disconnected");
|
||||
vibeAddMsg(
|
||||
"system",
|
||||
"⚠️ Could not connect to backend. You can still plan offline.",
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function vibeSendWs(content) {
|
||||
if (vibeWs && vibeWs.readyState === WebSocket.OPEN) {
|
||||
vibeWs.send(
|
||||
JSON.stringify({
|
||||
bot_id: vibeBotId,
|
||||
user_id: vibeUserId,
|
||||
session_id: vibeSessionId,
|
||||
channel: "web",
|
||||
content: content,
|
||||
message_type: 1,
|
||||
timestamp: new Date().toISOString(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function connectTaskProgressWs(taskId) {
|
||||
var proto = location.protocol === "https:" ? "wss://" : "ws://";
|
||||
var url =
|
||||
proto +
|
||||
location.host +
|
||||
"/ws/task-progress" +
|
||||
(taskId ? "/" + taskId : "");
|
||||
if (taskProgressWs) {
|
||||
try {
|
||||
taskProgressWs.close();
|
||||
} catch (ignore) { }
|
||||
}
|
||||
taskProgressWs = new WebSocket(url);
|
||||
|
||||
taskProgressWs.onmessage = function (event) {
|
||||
try {
|
||||
var data = JSON.parse(event.data);
|
||||
if (data.type === "connected") return;
|
||||
|
||||
if (
|
||||
data.event_type === "agent_thought" ||
|
||||
data.step === "agent_thought"
|
||||
) {
|
||||
var agentLabel = (data.details || "mantis_1").replace(
|
||||
"mantis_",
|
||||
"Mantis #",
|
||||
);
|
||||
vibeAddMsg(
|
||||
"system",
|
||||
"💭 " +
|
||||
agentLabel +
|
||||
": " +
|
||||
esc(data.text || data.message || ""),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
data.event_type === "agent_update" ||
|
||||
data.step === "agent_update"
|
||||
) {
|
||||
try {
|
||||
var info =
|
||||
typeof data.details === "string"
|
||||
? JSON.parse(data.details)
|
||||
: data.details;
|
||||
if (info) {
|
||||
updateAgentCard(
|
||||
info.agent_id,
|
||||
info.status,
|
||||
info.detail,
|
||||
);
|
||||
}
|
||||
} catch (ignore) { }
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
data.event_type === "task_node" ||
|
||||
data.step === "task_node"
|
||||
) {
|
||||
try {
|
||||
var nodeInfo =
|
||||
typeof data.details === "string"
|
||||
? JSON.parse(data.details)
|
||||
: data.details;
|
||||
if (nodeInfo) {
|
||||
addTaskNode(
|
||||
nodeInfo.title || data.message || "Task",
|
||||
nodeInfo.description || "",
|
||||
{
|
||||
status: nodeInfo.status || "Planning",
|
||||
estimated_files:
|
||||
nodeInfo.estimated_files,
|
||||
estimated_time:
|
||||
nodeInfo.estimated_time,
|
||||
estimated_tokens:
|
||||
nodeInfo.estimated_tokens,
|
||||
fileList: nodeInfo.files || [],
|
||||
},
|
||||
);
|
||||
}
|
||||
} catch (ignore) {
|
||||
addTaskNode(data.message || "Task", "", {
|
||||
status: "Planning",
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
data.event_type === "step_progress" ||
|
||||
data.step === "step_progress"
|
||||
) {
|
||||
var pct = 0;
|
||||
if (data.current_step && data.total_steps) {
|
||||
pct = Math.round(
|
||||
(data.current_step / data.total_steps) * 100,
|
||||
);
|
||||
} else if (data.current && data.total) {
|
||||
pct = Math.round((data.current / data.total) * 100);
|
||||
}
|
||||
updateMantis1("working");
|
||||
var bar = document.querySelector(
|
||||
'.as-agent-card[data-agent-id="1"] .as-bar-fill',
|
||||
);
|
||||
if (bar) bar.style.width = pct + "%";
|
||||
|
||||
var stageMap = {
|
||||
Planning: "plan",
|
||||
Building: "build",
|
||||
Reviewing: "review",
|
||||
Deploying: "deploy",
|
||||
Monitoring: "monitor",
|
||||
};
|
||||
var stageLabel = data.message || "";
|
||||
var tabStage = stageMap[stageLabel];
|
||||
if (tabStage) {
|
||||
var allTabs =
|
||||
document.querySelectorAll(".vibe-pipeline-tab");
|
||||
allTabs.forEach(function (t) {
|
||||
t.classList.remove("active");
|
||||
});
|
||||
var activeTab = document.querySelector(
|
||||
'.vibe-pipeline-tab[data-stage="' +
|
||||
tabStage +
|
||||
'"]',
|
||||
);
|
||||
if (activeTab) activeTab.classList.add("active");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
data.event_type === "pipeline_complete" ||
|
||||
data.step === "pipeline_complete"
|
||||
) {
|
||||
updateMantis1("done");
|
||||
vibeAddMsg(
|
||||
"system",
|
||||
"✅ Pipeline complete — all stages finished",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.event_type === "manifest_update") {
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("Task progress parse error:", e);
|
||||
}
|
||||
};
|
||||
|
||||
taskProgressWs.onerror = function () { };
|
||||
taskProgressWs.onclose = function () { };
|
||||
}
|
||||
517
botui/ui/suite/vibe/vibe.css
Normal file
517
botui/ui/suite/vibe/vibe.css
Normal file
|
|
@ -0,0 +1,517 @@
|
|||
@keyframes nodeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px) scale(0.95);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes float {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
}
|
||||
|
||||
.vibe-quick-btn:hover {
|
||||
border-color: var(--accent) !important;
|
||||
color: var(--accent) !important;
|
||||
background: rgba(132, 214, 105, 0.06) !important;
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.vibe-quick-btn {
|
||||
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;
|
||||
}
|
||||
|
||||
.as-logo-section {
|
||||
padding: 16px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.as-logo-section h2 {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
color: var(--text);
|
||||
font-weight: 800;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.as-logo-icons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
color: var(--text-muted);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.as-logo-icons span {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.as-create-btn-full {
|
||||
width: calc(100% - 16px);
|
||||
margin: 8px;
|
||||
}
|
||||
|
||||
.mcp-overlay {
|
||||
width: 280px;
|
||||
margin-left: 280px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
top: 60px;
|
||||
pointer-events: none;
|
||||
z-index: 50;
|
||||
}
|
||||
|
||||
.mcp-overlay-inner {
|
||||
pointer-events: auto;
|
||||
position: absolute;
|
||||
bottom: 24px;
|
||||
left: 24px;
|
||||
right: 24px;
|
||||
}
|
||||
|
||||
.vibe-canvas-bg {
|
||||
background: var(--bg, #fdfdfd);
|
||||
background-image: radial-gradient(var(--border) 1px, transparent 1px);
|
||||
background-size: 20px 20px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.vibe-canvas-header {
|
||||
padding: 16px 24px;
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
letter-spacing: 0.5px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
background: var(--surface);
|
||||
opacity: 0.95;
|
||||
backdrop-filter: blur(4px);
|
||||
}
|
||||
|
||||
.vibe-canvas-header-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.vibe-toolbar-btn {
|
||||
border: 1px solid var(--accent);
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
border-radius: 4px;
|
||||
padding: 2px 8px;
|
||||
cursor: pointer;
|
||||
margin-right: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.vibe-toolbar-btn-primary {
|
||||
border: 1px solid var(--accent);
|
||||
background: var(--accent);
|
||||
color: var(--bg);
|
||||
border-radius: 4px;
|
||||
padding: 2px 8px;
|
||||
cursor: pointer;
|
||||
margin-right: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.vibe-zoom-btn {
|
||||
border: 1px solid var(--border);
|
||||
background: var(--bg);
|
||||
border-radius: 4px;
|
||||
padding: 2px 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vibe-zoom-label {
|
||||
font-size: 11px;
|
||||
margin: 0 8px;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.vibe-steps-container {
|
||||
padding: 40px;
|
||||
display: none;
|
||||
gap: 60px;
|
||||
align-items: flex-start;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.vibe-canvas-empty {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 16px;
|
||||
padding: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.vibe-empty-icon {
|
||||
font-size: 56px;
|
||||
animation: float 3s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.vibe-empty-title {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 800;
|
||||
color: var(--text);
|
||||
font-family: "Fira Code", monospace;
|
||||
}
|
||||
|
||||
.vibe-empty-desc {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
color: var(--text-muted);
|
||||
max-width: 440px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.vibe-quick-btns {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.vibe-chat-overlay {
|
||||
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(--text);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.vibe-chat-header {
|
||||
padding: 12px 16px;
|
||||
background: var(--surface-hover);
|
||||
border-bottom: 1px solid var(--border);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.vibe-chat-header-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.vibe-chat-agent-name {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.vibe-chat-status-badge {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
background: var(--surface-hover);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.vibe-chat-messages {
|
||||
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;
|
||||
}
|
||||
|
||||
.vibe-chat-tip {
|
||||
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;
|
||||
}
|
||||
|
||||
.vibe-chat-footer {
|
||||
padding: 12px;
|
||||
border-top: 1px solid var(--border);
|
||||
background: var(--surface);
|
||||
}
|
||||
|
||||
.vibe-chat-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: var(--surface-hover);
|
||||
padding: 8px 12px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.vibe-chat-attach {
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.vibe-chat-input {
|
||||
flex: 1;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--text);
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
font-family: "Segoe UI", system-ui, sans-serif;
|
||||
}
|
||||
|
||||
.vibe-chat-send {
|
||||
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;
|
||||
}
|
||||
|
||||
.vibe-panel-modal {
|
||||
display: none;
|
||||
position: absolute;
|
||||
inset: 24px;
|
||||
z-index: 50;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.vibe-panel-close {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 16px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.vibe-terminal-modal {
|
||||
display: none;
|
||||
position: absolute;
|
||||
bottom: 24px;
|
||||
left: 300px;
|
||||
right: 24px;
|
||||
height: 350px;
|
||||
z-index: 50;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.vibe-terminal-close {
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
right: 8px;
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.vibe-deployment-panel-inner {
|
||||
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;
|
||||
}
|
||||
|
||||
.vibe-deploy-header {
|
||||
padding: 24px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.vibe-deploy-header h2 {
|
||||
margin: 0;
|
||||
font-size: 20px;
|
||||
color: var(--text);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.vibe-deploy-header p {
|
||||
margin: 8px 0 0;
|
||||
font-size: 14px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.vibe-deploy-body {
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.vibe-deploy-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.deployment-option {
|
||||
padding: 20px;
|
||||
border: 2px solid var(--border);
|
||||
border-radius: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.deployment-option-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.deployment-option-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.deployment-option-header span.label {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.deployment-option ul {
|
||||
margin: 0;
|
||||
padding-left: 20px;
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.vibe-deploy-section h3 {
|
||||
margin: 0 0 16px;
|
||||
font-size: 14px;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.vibe-deploy-field {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.vibe-deploy-field label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.vibe-deploy-field input[type="text"],
|
||||
.vibe-deploy-field select {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.vibe-deploy-field-hint {
|
||||
font-size: 12px;
|
||||
color: var(--text-muted);
|
||||
margin-top: 4px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.vibe-deploy-checkbox label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 13px;
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vibe-deploy-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.vibe-deploy-cancel {
|
||||
padding: 10px 20px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
background: transparent;
|
||||
color: var(--text);
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.vibe-deploy-submit {
|
||||
padding: 10px 20px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
background: var(--accent);
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue