@@ -32,6 +32,12 @@ interface ToolCall {
3232 isExpanded ?: boolean ;
3333}
3434
35+ interface StreamLine {
36+ type : "text" | "tool" | "result" | "error" ;
37+ content : string ;
38+ timestamp : number ;
39+ }
40+
3541export function PolicyChat ( ) {
3642 const { baseUrl } = useApi ( ) ;
3743 const [ messages , setMessages ] = useState < Message [ ] > ( [ ] ) ;
@@ -41,6 +47,7 @@ export function PolicyChat() {
4147 const [ mcpConnected , setMcpConnected ] = useState < boolean | null > ( null ) ;
4248 const [ displayedContent , setDisplayedContent ] = useState ( "" ) ;
4349 const [ isTyping , setIsTyping ] = useState ( false ) ;
50+ const [ streamLines , setStreamLines ] = useState < StreamLine [ ] > ( [ ] ) ;
4451 const messagesEndRef = useRef < HTMLDivElement > ( null ) ;
4552 const fullContentRef = useRef ( "" ) ;
4653
@@ -81,6 +88,7 @@ export function PolicyChat() {
8188 setMcpConnected ( null ) ;
8289 setDisplayedContent ( "" ) ;
8390 setIsTyping ( false ) ;
91+ setStreamLines ( [ ] ) ;
8492 fullContentRef . current = "" ;
8593
8694 // Add user message
@@ -154,6 +162,11 @@ export function PolicyChat() {
154162 assistantText += item . text + "\n" ;
155163 fullContentRef . current = assistantText . trim ( ) ;
156164 setIsTyping ( true ) ;
165+ // Add to stream lines
166+ setStreamLines ( ( prev ) => [
167+ ...prev ,
168+ { type : "text" , content : item . text , timestamp : Date . now ( ) } ,
169+ ] ) ;
157170 setMessages ( ( prev ) => {
158171 const newMessages = [ ...prev ] ;
159172 const lastIndex = newMessages . length - 1 ;
@@ -173,6 +186,11 @@ export function PolicyChat() {
173186 } ;
174187 toolCalls . push ( toolCall ) ;
175188 setCurrentToolCalls ( [ ...toolCalls ] ) ;
189+ // Add tool use to stream lines
190+ setStreamLines ( ( prev ) => [
191+ ...prev ,
192+ { type : "tool" , content : item . name , timestamp : Date . now ( ) } ,
193+ ] ) ;
176194 }
177195 }
178196 }
@@ -404,6 +422,36 @@ export function PolicyChat() {
404422 ) ;
405423 } ) }
406424
425+ { /* Live streaming log */ }
426+ { isLoading && streamLines . length > 0 && (
427+ < div className = "bg-[var(--color-surface-sunken)] rounded-xl p-3 space-y-1 font-mono text-xs" >
428+ < div className = "text-xs font-medium text-[var(--color-text-muted)] mb-2" >
429+ Live output
430+ </ div >
431+ { streamLines . map ( ( line , i ) => (
432+ < div
433+ key = { i }
434+ className = { `flex items-start gap-2 ${
435+ line . type === "tool"
436+ ? "text-amber-600"
437+ : line . type === "error"
438+ ? "text-red-500"
439+ : "text-[var(--color-text-secondary)]"
440+ } `}
441+ >
442+ < span className = "text-[var(--color-text-muted)] select-none" > { ">" } </ span >
443+ < span className = "whitespace-pre-wrap break-words" >
444+ { line . type === "tool" ? `[Using: ${ line . content } ]` : line . content }
445+ </ span >
446+ </ div >
447+ ) ) }
448+ < div className = "flex items-center gap-2 text-[var(--color-text-muted)]" >
449+ < span className = "select-none" > { ">" } </ span >
450+ < span className = "inline-block w-2 h-3 bg-[var(--color-pe-green)] animate-pulse" />
451+ </ div >
452+ </ div >
453+ ) }
454+
407455 { /* Live tool calls */ }
408456 { currentToolCalls . length > 0 && (
409457 < div className = "bg-[var(--color-surface-sunken)] rounded-xl p-3 space-y-2" >
0 commit comments