fix: Add switcher support to chat page - render switcher chips from BotResponse, auto-activate on switch_context suggestion click, include active_switchers in WS payload (#495)
All checks were successful
Botlib CI / build (push) Successful in 6s
BotServer CI / build (push) Successful in 3m11s
Bottest CI / build (push) Successful in 29s
BotUI CI / build (push) Successful in 1m30s

This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2026-04-24 18:17:29 -03:00
parent c34b719515
commit 1bb96f1923

View file

@ -22,7 +22,13 @@
<main id="messages"></main> <main id="messages"></main>
<footer> <footer>
<div class="chat-footer-content">
<div class="suggestions-container" id="suggestions"></div> <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-dropdown" id="mentionDropdown"> <div class="mention-dropdown" id="mentionDropdown">
<div class="mention-header"> <div class="mention-header">
<span class="mention-title" data-i18n="chat-mention-title" <span class="mention-title" data-i18n="chat-mention-title"
@ -175,6 +181,8 @@
var currentSessionId = null; var currentSessionId = null;
var currentUserId = null; var currentUserId = null;
var currentBotId = "default"; var currentBotId = "default";
var activeSwitchers = new Set();
var switcherDefinitions = [];
var currentBotName = "default"; var currentBotName = "default";
var isStreaming = false; var isStreaming = false;
var streamingMessageId = null; var streamingMessageId = null;
@ -196,6 +204,68 @@
var div = document.createElement("div"); var div = document.createElement("div");
div.textContent = text; div.textContent = text;
return div.innerHTML; return div.innerHTML;
}
var SWITCHER_ICONS = {
tables: "\u{1F4CA}", infographic: "\u{1F4CD}", cards: "\u{1F0CF}",
list: "\u{1F4CB}", comparison: "\u2696", timeline: "\u{23F0}",
markdown: "\u{1F4DD}", chart: "\u{1F4C8}"
};
function renderSwitcherChips() {
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 || '#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) {
if (activeSwitchers.has(switcherId)) {
activeSwitchers.delete(switcherId);
} else {
activeSwitchers.add(switcherId);
}
renderSwitcherChips();
}
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 || SWITCHER_ICONS[sw.id] || "\u{1F500}",
color: sw.color || "#666"
});
existingIds[sw.id] = true;
}
});
renderSwitcherChips();
var container = document.getElementById("switchers");
if (container && 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";
}
} }
// Scroll handling // Scroll handling
@ -831,6 +901,9 @@ function updateStreaming(content) {
isStreaming = false; isStreaming = false;
if (data.suggestions && Array.isArray(data.suggestions) && data.suggestions.length > 0) { if (data.suggestions && Array.isArray(data.suggestions) && data.suggestions.length > 0) {
renderSuggestions(data.suggestions); renderSuggestions(data.suggestions);
}
if (data.switchers && Array.isArray(data.switchers) && data.switchers.length > 0) {
renderBotSwitchers(data.switchers);
} }
} else { } else {
if (!isStreaming) { if (!isStreaming) {
@ -901,9 +974,16 @@ function updateStreaming(content) {
channel: "web", channel: "web",
content: action.tool, content: action.tool,
message_type: 6, // TOOL_EXEC message_type: 6, // TOOL_EXEC
active_switchers: Array.from(activeSwitchers),
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
})); }));
return; return;
} else if (action.type === "switch_context" && action.switcher) {
if (!activeSwitchers.has(action.switcher)) {
activeSwitchers.add(action.switcher);
renderSwitcherChips();
}
window.sendMessage(sugg.text);
} else if (action.type === "send_message") { } else if (action.type === "send_message") {
window.sendMessage( window.sendMessage(
action.message || sugg.text, action.message || sugg.text,
@ -971,6 +1051,7 @@ function sendMessage(messageContent) {
channel: "web", channel: "web",
content: content, content: content,
message_type: MessageType.USER, message_type: MessageType.USER,
active_switchers: Array.from(activeSwitchers),
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
}), }),
); );