Skip to content

Commit d386b5d

Browse files
feat: add live streaming log to agent UI
1 parent 3742cb2 commit d386b5d

File tree

1 file changed

+48
-0
lines changed

1 file changed

+48
-0
lines changed

docs/src/components/policy-chat.tsx

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
3541
export 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

Comments
 (0)