The Chat Panel provides an interactive interface to query your infrastructure using natural language. Powered by a RAG (Retrieval-Augmented Generation) knowledge base, it allows you to ask questions about commands, logs, configurations, and historical incidents.
Overview
The chat interface combines:
LLM-powered responses using OpenAI or compatible models
RAG knowledge base with embeddings of logs, commands, and documentation
Streaming responses for real-time feedback
Thinking process visualization showing the agent’s reasoning steps
Natural Language Queries Ask questions in plain English or Spanish
Context-Aware Responses Uses RAG to retrieve relevant historical data
Streaming Responses See responses appear in real-time with SSE
Thinking Steps Expandable view of the agent’s reasoning process
Chat Panel Component
The ChatPanel component manages conversation state and streaming:
// components/dashboard/ChatPanel.tsx
interface Message {
role : 'user' | 'assistant' ;
content : string ;
thinking ?: string []; // Log of thinking steps
}
export function ChatPanel () {
const [ input , setInput ] = useState ( '' );
const [ loading , setLoading ] = useState ( false );
const [ messages , setMessages ] = useState < Message []>([
{
role: 'assistant' ,
content: 'Hola, soy Sentinel AI. ¿En qué puedo ayudarte hoy?'
}
]);
const handleSend = async () => {
const userMsg = input ;
setInput ( '' );
// Add user message and placeholder for assistant
setMessages ( prev => [
... prev ,
{ role: 'user' , content: userMsg },
{ role: 'assistant' , content: '' , thinking: [] }
]);
setLoading ( true );
try {
await api . chatStream ( userMsg , ( event ) => {
setMessages ( prev => {
const newMessages = [ ... prev ];
const lastMsg = { ... newMessages [ newMessages . length - 1 ] };
if ( event . event === 'thinking' ) {
// Add thinking step
const step = event . data as string ;
lastMsg . thinking = [ ... ( lastMsg . thinking || []), step ];
} else if ( event . event === 'message' ) {
// Append message chunk
lastMsg . content += event . data as string ;
}
newMessages [ newMessages . length - 1 ] = lastMsg ;
return newMessages ;
});
});
} finally {
setLoading ( false );
}
};
return (
< Card className = "flex flex-col h-[600px]" >
< CardHeader >
< CardTitle > RAG Knowledge Base </ CardTitle >
</ CardHeader >
< CardContent className = "flex-1 flex flex-col overflow-hidden" >
< ScrollArea className = "flex-1" >
{ messages . map (( m , i ) => (
< MessageBubble key = { i } message = { m } />
)) }
</ ScrollArea >
< form onSubmit = { handleSend } className = "flex gap-2" >
< Input
value = { input }
onChange = { ( e ) => setInput ( e . target . value ) }
placeholder = "Ask about infrastructure, commands, or logs..."
disabled = { loading }
/>
< Button type = "submit" disabled = { loading } >
< Send className = "w-4 h-4" />
</ Button >
</ form >
</ CardContent >
</ Card >
);
}
Server-Sent Events (SSE)
The chat uses SSE for streaming responses from the backend:
// lib/api.ts
export const api = {
chatStream : async ( query : string , onEvent : ( event : ChatEvent ) => void ) => {
const response = await fetch ( ` ${ API_URL } /chat` , {
method: 'POST' ,
headers: { 'Content-Type' : 'application/json' },
body: JSON . stringify ({ query })
});
const reader = response . body . getReader ();
const decoder = new TextDecoder ();
let buffer = '' ;
while ( true ) {
const { done , value } = await reader . read ();
if ( done ) break ;
buffer += decoder . decode ( value , { stream: true });
const parts = buffer . split ( ' \n\n ' );
buffer = parts . pop () || '' ;
for ( const block of parts ) {
if ( ! block . trim ()) continue ;
const lines = block . split ( ' \n ' );
let eventType = '' ;
let data = '' ;
for ( const line of lines ) {
if ( line . startsWith ( 'event: ' )) eventType = line . substring ( 7 );
if ( line . startsWith ( 'data: ' )) data = line . substring ( 6 );
}
if ( eventType && data ) {
onEvent ({ event: eventType , data: JSON . parse ( data ) });
}
}
}
}
};
SSE allows the server to push multiple events over a single HTTP connection, reducing latency compared to polling.
Backend Chat Endpoint
The FastAPI backend streams responses using the RAG knowledge base:
# src/api/routes.py
@router.post ( "/chat" )
def chat ( request : ChatRequest):
if not knowledge.kb:
raise HTTPException(
status_code = 503 ,
detail = "Knowledge base is initializing. Try again in a few seconds."
)
def event_stream ():
try :
for event in knowledge.kb.stream_query(request.query):
yield f "event: { event[ 'event' ] } \n data: { json.dumps(event[ 'data' ]) } \n\n "
except Exception as e:
error_data = json.dumps({ "error" : str (e)})
yield f "event: error \n data: { error_data } \n\n "
return StreamingResponse(event_stream(), media_type = "text/event-stream" )
Event Types
The chat endpoint emits three types of events:
thinking Reasoning steps showing how the agent processes the query
message Content chunks that form the final response
error Error messages if the query fails
Each SSE event follows this structure:
event: thinking
data: {"step": "Searching knowledge base for 'nginx errors'"}
event: message
data: {"chunk": "Based on recent logs, nginx failed due to "}
event: message
data: {"chunk": "port 80 being already in use."}
event: done
data: {}
Thinking Process Visualization
The thinking steps are displayed in a collapsible panel:
// components/dashboard/ChatPanel.tsx
function ThinkingProcess ({ steps , finished } : {
steps : string [],
finished : boolean
}) {
const [ isOpen , setIsOpen ] = useState ( ! finished );
useEffect (() => {
// Auto-expand while thinking, collapse when done
const timer = setTimeout (() => {
setIsOpen ( ! finished );
}, 0 );
return () => clearTimeout ( timer );
}, [ finished ]);
return (
< Collapsible open = { isOpen } onOpenChange = { setIsOpen } >
< CollapsibleTrigger >
{ isOpen ? < ChevronDown /> : < ChevronRight /> }
< Brain className = "h-3 w-3 text-purple-400" />
{ finished ? "Processed successfully" : "Reasoning..." }
</ CollapsibleTrigger >
< CollapsibleContent >
{ steps . map (( step , idx ) => (
< div key = { idx } className = "text-[10px] text-muted-foreground" >
< span className = "w-1.5 h-1.5 rounded-full bg-purple-500/50" />
{ step }
</ div >
)) }
{ ! finished && (
< div className = "animate-pulse" >
< Loader2 className = "animate-spin" />
Thinking...
</ div >
) }
</ CollapsibleContent >
</ Collapsible >
);
}
The thinking process automatically collapses when the response is complete, reducing visual clutter.
Markdown Rendering
Responses are rendered using ReactMarkdown with GitHub Flavored Markdown:
import ReactMarkdown from 'react-markdown' ;
import remarkGfm from 'remark-gfm' ;
< ReactMarkdown
remarkPlugins = { [ remarkGfm ] }
components = { {
code : ({ inline , children , ... props }) => {
return inline ? (
< code className = "bg-muted px-1 rounded font-mono" >
{ children }
</ code >
) : (
< code className = "block bg-black/50 p-3 rounded-lg font-mono" >
{ children }
</ code >
);
},
table : ( props ) => (
< div className = "overflow-auto rounded border" >
< table className = "w-full text-xs" { ... props } />
</ div >
)
} }
>
{ message . content }
</ ReactMarkdown >
Example Queries
Here are some example questions you can ask:
Service Status “Why did nginx fail last night?”
Command History “Show me the last commands executed for postgresql”
Configuration “What services are currently monitored?”
Troubleshooting “How do I fix a database connection error?”
RAG Knowledge Base
The chat is powered by a vector database that stores:
Agent memory - Past diagnoses, commands, and results
Configuration - Service definitions and monitoring rules
Documentation - Command references and troubleshooting guides
Logs - Historical log entries with embeddings
The knowledge base is initialized asynchronously when the server starts. If you see a 503 error, wait a few seconds for initialization to complete.
The chat automatically scrolls to the latest message:
const scrollEndRef = useRef < HTMLDivElement >( null );
useEffect (() => {
scrollEndRef . current ?. scrollIntoView ({ behavior: "smooth" });
}, [ messages ]);
return (
< ScrollArea >
{ messages . map ( m => < MessageBubble message = { m } /> ) }
< div ref = { scrollEndRef } />
</ ScrollArea >
);
Error Handling
Errors are displayed inline in the chat:
if ( event . event === 'error' ) {
const errData = event . data as { error : string };
lastMsg . content += ` \n\n **Error:** ${ errData . error } ` ;
}
If the chat consistently returns errors, verify that:
The backend server is running
The OpenAI API key is configured
The knowledge base has been initialized
Message Styling
User and assistant messages use different styles:
< div className = { `rounded-lg px-4 py-3 ${
message . role === 'user'
? 'bg-primary text-primary-foreground'
: 'bg-muted border text-foreground'
} ` } >
{ message . content }
</ div >
Next Steps
Knowledge Base Learn how the RAG system works
Memory System Understand how agent memory is stored
AI Models Configure the language model