Enhance streaming with events and warning API

- Introduce event-driven streaming with thinking_start, thinking_end,
  and warn events; skip sending analysis content to clients
- Add /api/warn endpoint to dispatch warnings for sessions and channels;
  web UI displays alerts
- Emit session_start/session_end events over WebSocket and instrument
  logging throughout orchestration
- Update web client: show thinking indicator and warning banners; switch
  LiveKit client URL to CDN
- Extend BotOrchestrator with send_event and send_warning, expand
  session/tool workflow
- Improve REST endpoints for sessions/history with better logging and
  error handling
- Update docs and prompts: DEV.md usage note; adjust dev build_prompt
  script
This commit is contained in:
Rodrigo Rodriguez (Pragmatismo) 2025-10-13 00:31:08 -03:00
parent 712266a9f8
commit a7c74b837e
4 changed files with 749 additions and 62 deletions

View file

@ -1,3 +1,9 @@
# DEV
curl -sSL https://get.livekit.io | bash
livekit-server --dev
# Util # Util

View file

@ -9,8 +9,8 @@ echo "Consolidated LLM Context" > "$OUTPUT_FILE"
prompts=( prompts=(
"../../prompts/dev/shared.md" "../../prompts/dev/shared.md"
"../../Cargo.toml" "../../Cargo.toml"
"../../prompts/dev/fix.md" #../../prompts/dev/fix.md"
#"../../prompts/dev/generation.md" "../../prompts/dev/generation.md"
) )
for file in "${prompts[@]}"; do for file in "${prompts[@]}"; do
@ -23,23 +23,24 @@ dirs=(
#"automation" #"automation"
#"basic" #"basic"
"bot" "bot"
#"channels" "channels"
#"config" #"config"
#"context" #"context"
#"email" #"email"
#"file" #"file"
#"llm" "llm"
#"llm_legacy" #"llm_legacy"
#"org" #"org"
#"session" "session"
"shared" "shared"
#"tests" #"tests"
#"tools" #"tools"
#"web_automation" #"web_automation"
#"whatsapp" "whatsapp"
) )
for dir in "${dirs[@]}"; do for dir in "${dirs[@]}"; do
find "$PROJECT_ROOT/src/$dir" -name "*.rs" | while read file; do find "$PROJECT_ROOT/src/$dir" -name "*.rs" | while read file; do
echo $file >> "$OUTPUT_FILE"
cat "$file" >> "$OUTPUT_FILE" cat "$file" >> "$OUTPUT_FILE"
echo "" >> "$OUTPUT_FILE" echo "" >> "$OUTPUT_FILE"
done done

File diff suppressed because it is too large Load diff

View file

@ -7,7 +7,7 @@
<script src="https://cdn.tailwindcss.com"></script> <script src="https://cdn.tailwindcss.com"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.2/anime.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.2/anime.min.js"></script>
<script src="https://unpkg.com/livekit-client@latest/dist/livekit-client.js"></script> <script src="https://cdn.jsdelivr.net/npm/livekit-client/dist/livekit-client.umd.min.js"></script>
<style> <style>
@import url("https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;800&family=Inter:wght@400;600&display=swap"); @import url("https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;800&family=Inter:wght@400;600&display=swap");
@ -299,6 +299,57 @@
font-family: "Orbitron", monospace; font-family: "Orbitron", monospace;
font-weight: 600; font-weight: 600;
} }
.thinking-indicator {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 16px;
background: rgba(255, 215, 0, 0.1);
border: 1px solid rgba(255, 215, 0, 0.3);
border-radius: 12px;
margin: 10px auto;
max-width: 800px;
animation: neonPulse 2s infinite;
box-shadow: 0 0 20px rgba(255, 215, 0, 0.3);
}
@keyframes neonPulse {
0%,
100% {
box-shadow:
0 0 5px rgba(255, 215, 0, 0.3),
0 0 10px rgba(255, 215, 0, 0.2);
}
50% {
box-shadow:
0 0 10px rgba(255, 215, 0, 0.5),
0 0 20px rgba(255, 215, 0, 0.3),
0 0 30px rgba(255, 215, 0, 0.1);
}
}
.warning-message {
background: rgba(255, 69, 0, 0.2);
border: 1px solid rgba(255, 69, 0, 0.5);
color: #ff4500;
padding: 12px 16px;
border-radius: 12px;
margin: 10px auto;
max-width: 800px;
text-align: center;
animation: flash 0.5s ease 3;
}
@keyframes flash {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
</style> </style>
</head> </head>
<body class="relative overflow-hidden flex"> <body class="relative overflow-hidden flex">
@ -398,6 +449,8 @@
let mediaRecorder = null; let mediaRecorder = null;
let audioChunks = []; let audioChunks = [];
let streamingMessageId = null; let streamingMessageId = null;
let isThinking = false;
let thinkingIndicatorId = null;
const messagesDiv = document.getElementById("messages"); const messagesDiv = document.getElementById("messages");
const input = document.getElementById("messageInput"); const input = document.getElementById("messageInput");
@ -473,6 +526,14 @@
ws.onmessage = function (event) { ws.onmessage = function (event) {
const response = JSON.parse(event.data); const response = JSON.parse(event.data);
// Handle event messages (thinking_start, thinking_end, warn)
if (response.message_type === 2) {
const eventData = JSON.parse(response.content);
handleEvent(eventData.event, eventData.data);
return;
}
// Handle regular messages
if (!response.is_complete) { if (!response.is_complete) {
if (!isStreaming) { if (!isStreaming) {
isStreaming = true; isStreaming = true;
@ -497,6 +558,81 @@
}; };
} }
function handleEvent(eventType, eventData) {
console.log("Event received:", eventType, eventData);
switch (eventType) {
case "thinking_start":
showThinkingIndicator();
isThinking = true;
break;
case "thinking_end":
hideThinkingIndicator();
isThinking = false;
break;
case "warn":
showWarning(eventData.message);
break;
default:
console.log("Unknown event type:", eventType);
}
}
function showThinkingIndicator() {
if (isThinking) return;
const emptyState = document.getElementById("emptyState");
if (emptyState) emptyState.remove();
const thinkingDiv = document.createElement("div");
thinkingDiv.id = "thinking-indicator";
thinkingDiv.className = "thinking-indicator";
thinkingDiv.innerHTML = `
<div class="typing-dots flex gap-2">
<div class="typing-dot"></div>
<div class="typing-dot"></div>
<div class="typing-dot"></div>
</div>
<span class="text-yellow-300 font-semibold">Pensando...</span>
`;
messagesDiv.appendChild(thinkingDiv);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
isThinking = true;
thinkingIndicatorId = thinkingDiv.id;
}
function hideThinkingIndicator() {
if (!isThinking) return;
const thinkingDiv =
document.getElementById("thinking-indicator");
if (thinkingDiv) {
thinkingDiv.remove();
}
isThinking = false;
thinkingIndicatorId = null;
}
function showWarning(message) {
const warningDiv = document.createElement("div");
warningDiv.className = "warning-message";
warningDiv.innerHTML = `⚠️ ${message}`;
messagesDiv.appendChild(warningDiv);
messagesDiv.scrollTop = messagesDiv.scrollHeight;
// Remove warning after 5 seconds
setTimeout(() => {
if (warningDiv.parentNode) {
warningDiv.remove();
}
}, 5000);
}
function addMessage( function addMessage(
role, role,
content, content,
@ -539,6 +675,12 @@
function sendMessage() { function sendMessage() {
const message = input.value.trim(); const message = input.value.trim();
if (!message || !ws || ws.readyState !== WebSocket.OPEN) return; if (!message || !ws || ws.readyState !== WebSocket.OPEN) return;
// Hide thinking indicator if it's showing
if (isThinking) {
hideThinkingIndicator();
}
addMessage("user", message); addMessage("user", message);
ws.send(message); ws.send(message);
input.value = ""; input.value = "";
@ -733,12 +875,27 @@
// Neon text animation // Neon text animation
gsap.to(".neon-text", { gsap.to(".neon-text", {
textShadow: textShadow:
"0 0 25px var(--gb-glow),0 0 50px var(--gb-glow),0 0 100px rgba(255,215,0,0.8)", "0 0 25px var(--dante-glow),0 0 50px var(--dante-glow),0 0 100px rgba(255,215,0,0.8)",
repeat: -1, repeat: -1,
yoyo: true, yoyo: true,
duration: 1.8, duration: 1.8,
ease: "power1.inOut", ease: "power1.inOut",
}); });
// Test warning functionality
window.testWarning = function () {
fetch("/api/warn", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
session_id: currentSessionId || "default",
channel: "web",
message: "Esta é uma mensagem de teste de aviso!",
}),
});
};
</script> </script>
</body> </body>
</html> </html>