Fix: start.bas não executa e HTML truncado

- Remove streaming de chunks LLM, acumula resposta completa antes de enviar
- Corrige variável 'action' para 'actionData' no click handler de suggestions
- Adiciona fallback window.sendMessage() se WebSocket não estiver aberto
- Adiciona guard DOMContentLoaded no chat-init.js
- Adiciona cache-busting (?v=4) no chat.html

Impacto:
- start.bas executa corretamente ao conectar WebSocket
- HTML não é mais truncado (tags fecham corretamente)
- Sugestões executam tool invocations via WebSocket
This commit is contained in:
Rodrigo Rodriguez 2026-05-01 02:02:57 -03:00
parent 5a6f062794
commit 73e0121d0b
4 changed files with 160 additions and 145 deletions

View file

@ -1346,30 +1346,9 @@ while let Some(chunk) = stream_rx.recv().await {
} }
if !in_analysis { if !in_analysis {
full_response.push_str(&chunk); // Accumulate full response - DO NOT send chunks
full_response.push_str(&chunk);
// Send immediately without buffering }
let response = BotResponse {
bot_id: message.bot_id.clone(),
user_id: message.user_id.clone(),
session_id: message.session_id.clone(),
channel: message.channel.clone(),
content: chunk.clone(),
message_type: MessageType::BOT_RESPONSE,
stream_token: None,
is_complete: false,
suggestions: Vec::new(),
switchers: Vec::new(),
context_name: None,
context_length: 0,
context_max_length: 0,
};
if response_tx.send(response).await.is_err() {
warn!("Response channel closed");
break;
}
}
} }
info!("llm_end: Streaming loop ended for session {}, chunk_count={}, full_response_len={}", session.id, chunk_count, full_response.len()); info!("llm_end: Streaming loop ended for session {}, chunk_count={}, full_response_len={}", session.id, chunk_count, full_response.len());
@ -1437,28 +1416,24 @@ if !in_analysis {
#[cfg(not(feature = "chat"))] #[cfg(not(feature = "chat"))]
let switchers: Vec<Switcher> = Vec::new(); let switchers: Vec<Switcher> = Vec::new();
// Content was already sent as streaming chunks. // Send accumulated full response (not streaming anymore)
// Sending full_response again would duplicate it (especially for WhatsApp which accumulates buffer). let final_response = BotResponse {
// The final response is just a signal that streaming is complete - it should not contain content. bot_id: message.bot_id,
let final_content = String::new(); user_id: message.user_id,
session_id: message.session_id,
let final_response = BotResponse { channel: message.channel,
bot_id: message.bot_id, content: full_response.clone(),
user_id: message.user_id, message_type: MessageType::BOT_RESPONSE,
session_id: message.session_id, stream_token: None,
channel: message.channel, is_complete: true,
content: final_content, suggestions,
message_type: MessageType::BOT_RESPONSE, switchers,
stream_token: None, context_name: None,
is_complete: true, context_length: 0,
suggestions, context_max_length: 0,
switchers,
context_name: None,
context_length: 0,
context_max_length: 0,
}; };
response_tx.send(final_response).await?; response_tx.send(final_response).await?;
Ok(()) Ok(())
} }

View file

@ -83,13 +83,21 @@ function setupEventHandlers() {
form.onsubmit = function (e) { e.preventDefault(); sendMessage(); return false; }; form.onsubmit = function (e) { e.preventDefault(); sendMessage(); return false; };
} }
if (input) { if (input) {
input.addEventListener("input", handleMentionInput); if (typeof handleMentionInput === 'function') {
input.onkeydown = function (e) { input.addEventListener("input", handleMentionInput);
if (handleMentionKeydown(e)) return; }
if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendMessage(); } if (typeof handleMentionKeydown === 'function') {
}; input.onkeydown = function (e) {
} if (handleMentionKeydown(e)) return;
if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendMessage(); }
};
} else {
input.onkeydown = function (e) {
if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); sendMessage(); }
};
}
}
if (sendBtn) { if (sendBtn) {
sendBtn.onclick = function (e) { e.preventDefault(); sendMessage(); }; sendBtn.onclick = function (e) { e.preventDefault(); sendMessage(); };
@ -110,16 +118,20 @@ function setupEventHandlers() {
}); });
} }
document.addEventListener("click", function (e) { document.addEventListener("click", function (e) {
if (!e.target.closest("#mentionDropdown") && !e.target.closest("#messageInput")) { if (!e.target.closest("#mentionDropdown") && !e.target.closest("#messageInput")) {
hideMentionDropdown(); if (typeof hideMentionDropdown === 'function') {
} hideMentionDropdown();
}); }
}
});
} }
function initChat() { function initChat() {
loadBotConfig(); if (typeof loadBotConfig === 'function') {
proceedWithChatInit(); loadBotConfig();
}
proceedWithChatInit();
} }
function showChatApp() { function showChatApp() {
@ -132,5 +144,15 @@ function showChatApp() {
window.showChatApp = showChatApp; window.showChatApp = showChatApp;
// Wait for DOM to be ready before initializing
if (typeof document !== 'undefined') {
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', function() {
setupEventHandlers(); setupEventHandlers();
initChat(); initChat();
});
} else {
setupEventHandlers();
initChat();
}
}

View file

@ -10,36 +10,50 @@ function renderSuggestions(suggestions) {
chip.className = "suggestion-chip"; chip.className = "suggestion-chip";
chip.textContent = suggestion.text || "Suggestion"; chip.textContent = suggestion.text || "Suggestion";
chip.onclick = (function (sugg) { chip.onclick = (function (sugg) {
return function () { return function () {
if (sugg.action) { console.log("[SUGGESTION] Clicked:", sugg.text, "Action:", sugg.action);
try { if (sugg.action) {
var action = typeof sugg.action === "string" try {
? JSON.parse(sugg.action) var actionData = typeof sugg.action === "string"
: sugg.action; ? JSON.parse(sugg.action)
: sugg.action;
if (action.type === "invoke_tool") { console.log("[SUGGESTION] Parsed action:", actionData);
ChatState.ws.send(JSON.stringify({
bot_id: ChatState.currentBotId, if (actionData.type === "invoke_tool") {
user_id: ChatState.currentUserId, console.log("[SUGGESTION] Invoking tool:", actionData.tool);
session_id: ChatState.currentSessionId, // Check if WebSocket is available
channel: "web", if (ChatState.ws && ChatState.ws.readyState === WebSocket.OPEN) {
content: action.tool, var msg = {
message_type: 6, bot_id: ChatState.currentBotId,
active_switchers: Array.from(ChatState.activeSwitchers), user_id: ChatState.currentUserId,
timestamp: new Date().toISOString(), session_id: ChatState.currentSessionId,
})); channel: "web",
return; content: actionData.tool,
} else if (action.type === "switch_context" && action.switcher) { message_type: 6,
if (!ChatState.activeSwitchers.has(action.switcher)) { active_switchers: Array.from(ChatState.activeSwitchers),
ChatState.activeSwitchers.add(action.switcher); timestamp: new Date().toISOString(),
renderSwitcherChips(); };
} console.log("[SUGGESTION] Sending via WS:", msg);
window.sendMessage(sugg.text); ChatState.ws.send(JSON.stringify(msg));
} else if (action.type === "send_message") { console.log("[SUGGESTION] Sent successfully");
window.sendMessage(action.message || sugg.text); } else {
} else if (action.type === "select_context") { console.log("[SUGGESTION] WS not available, fallback to sendMessage");
window.sendMessage(action.context); // Fallback: send as regular message if WS not available
window.sendMessage(sugg.text);
}
return;
} else if (actionData.type === "switch_context" && actionData.switcher) {
if (!ChatState.activeSwitchers.has(actionData.switcher)) {
ChatState.activeSwitchers.add(actionData.switcher);
renderSwitcherChips();
}
window.sendMessage(sugg.text);
} else if (actionData.type === "send_message") {
window.sendMessage(actionData.message || sugg.text);
} else if (actionData.type === "select_context") {
window.sendMessage(actionData.context);
} else { } else {
window.sendMessage(sugg.text); window.sendMessage(sugg.text);
} }

View file

@ -1,70 +1,74 @@
<link rel="stylesheet" href="/suite/chat/chat.css?v=3" /> <!DOCTYPE html>
<link rel="stylesheet" href="/suite/css/markdown-message.css" /> <html lang="pt-BR">
<link rel="stylesheet" href="/suite/css/chat-agent-mode.css" /> <head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chat - General Bots</title>
<link rel="stylesheet" href="/suite/chat/chat.css?v=4" />
<link rel="stylesheet" href="/suite/css/markdown-message.css" />
<link rel="stylesheet" href="/suite/css/chat-agent-mode.css" />
</head>
<body>
<div class="chat-layout" id="chat-app" style="opacity: 1; visibility: visible;">
<div class="chat-content-wrapper" id="chatContentWrapper">
<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="chat-layout" id="chat-app" style="opacity: 0; visibility: hidden;"> <main id="messages"></main>
<div class="chat-loading-overlay" id="chatLoadingOverlay">
<div class="chat-loading-spinner"></div> <footer>
<div class="chat-loading-text">Carregando...</div> <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-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">&#8593;</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> </div>
<div class="chat-content-wrapper" id="chatContentWrapper"> <div class="entity-card-tooltip" id="entityCardTooltip">
<div class="connection-status connecting" id="connectionStatus" style="display: none">
<span class="connection-status-dot"></span>
<span class="connection-text">Connecting...</span>
</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="switcherChips"></div>
</div>
</div>
<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">&#8593;</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"> <div class="entity-card-header">
<span class="entity-card-type"></span> <span class="entity-card-type"></span>
<span class="entity-card-status"></span> <span class="entity-card-status"></span>
</div> </div>
<div class="entity-card-title"></div> <div class="entity-card-title"></div>
<div class="entity-card-details"></div> <div class="entity-card-details"></div>
<div class="entity-card-actions"> <div class="entity-card-actions">
<button class="entity-card-btn" data-action="view" data-i18n="action-view">View</button> <button class="entity-card-btn" data-action="view" data-i18n="action-view">View</button>
</div> </div>
</div>
</div> </div>
<script src="/suite/js/vendor/marked.min.js"></script> <script src="/suite/js/vendor/marked.min.js"></script>
<script src="/suite/chat/chat-state.js"></script> <script src="/suite/chat/chat-state.js?v=4"></script>
<script src="/suite/chat/chat-switchers.js"></script> <script src="/suite/chat/chat-switchers.js?v=4"></script>
<script src="/suite/chat/chat-mentions.js"></script> <script src="/suite/chat/chat-mentions.js?v=4"></script>
<script src="/suite/chat/chat-messages.js"></script> <script src="/suite/chat/chat-messages.js?v=4"></script>
<script src="/suite/chat/chat-suggestions.js"></script> <script src="/suite/chat/chat-suggestions.js?v=4"></script>
<script src="/suite/chat/chat-theme.js"></script> <script src="/suite/chat/chat-theme.js?v=4"></script>
<script src="/suite/chat/chat-websocket.js"></script> <script src="/suite/chat/chat-websocket.js?v=4"></script>
<script src="/suite/chat/chat-init.js"></script> <script src="/suite/chat/chat-init.js?v=4"></script>
<script src="/suite/js/chat-agent-mode.js"></script> <script src="/suite/js/chat-agent-mode.js?v=4"></script>
</body>
</html>