story-kit: queue 235_story_show_agent_logs_for_a_story_in_expanded_work_item for merge
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
import * as React from "react";
|
||||
import Markdown from "react-markdown";
|
||||
import type { AgentEvent, AgentInfo, AgentStatusValue } from "../api/agents";
|
||||
import { agentsApi, subscribeAgentStream } from "../api/agents";
|
||||
import { api } from "../api/client";
|
||||
|
||||
const { useEffect, useRef, useState } = React;
|
||||
@@ -13,6 +15,13 @@ const STAGE_LABELS: Record<string, string> = {
|
||||
archived: "Archived",
|
||||
};
|
||||
|
||||
const STATUS_COLORS: Record<AgentStatusValue, string> = {
|
||||
running: "#3fb950",
|
||||
pending: "#e3b341",
|
||||
completed: "#aaa",
|
||||
failed: "#f85149",
|
||||
};
|
||||
|
||||
interface WorkItemDetailPanelProps {
|
||||
storyId: string;
|
||||
onClose: () => void;
|
||||
@@ -27,7 +36,11 @@ export function WorkItemDetailPanel({
|
||||
const [name, setName] = useState<string | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [agentInfo, setAgentInfo] = useState<AgentInfo | null>(null);
|
||||
const [agentLog, setAgentLog] = useState<string[]>([]);
|
||||
const [agentStatus, setAgentStatus] = useState<AgentStatusValue | null>(null);
|
||||
const panelRef = useRef<HTMLDivElement>(null);
|
||||
const cleanupRef = useRef<(() => void) | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
@@ -47,6 +60,61 @@ export function WorkItemDetailPanel({
|
||||
});
|
||||
}, [storyId]);
|
||||
|
||||
useEffect(() => {
|
||||
cleanupRef.current?.();
|
||||
cleanupRef.current = null;
|
||||
setAgentInfo(null);
|
||||
setAgentLog([]);
|
||||
setAgentStatus(null);
|
||||
|
||||
agentsApi
|
||||
.listAgents()
|
||||
.then((agents) => {
|
||||
const agent = agents.find((a) => a.story_id === storyId);
|
||||
if (!agent) return;
|
||||
setAgentInfo(agent);
|
||||
setAgentStatus(agent.status);
|
||||
|
||||
if (agent.status === "running" || agent.status === "pending") {
|
||||
const cleanup = subscribeAgentStream(
|
||||
storyId,
|
||||
agent.agent_name,
|
||||
(event: AgentEvent) => {
|
||||
switch (event.type) {
|
||||
case "status":
|
||||
setAgentStatus((event.status as AgentStatusValue) ?? null);
|
||||
break;
|
||||
case "output":
|
||||
setAgentLog((prev) => [...prev, event.text ?? ""]);
|
||||
break;
|
||||
case "done":
|
||||
setAgentStatus("completed");
|
||||
break;
|
||||
case "error":
|
||||
setAgentStatus("failed");
|
||||
setAgentLog((prev) => [
|
||||
...prev,
|
||||
`[ERROR] ${event.message ?? "Unknown error"}`,
|
||||
]);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
},
|
||||
);
|
||||
cleanupRef.current = cleanup;
|
||||
}
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
console.error("Failed to load agents:", err);
|
||||
});
|
||||
|
||||
return () => {
|
||||
cleanupRef.current?.();
|
||||
cleanupRef.current = null;
|
||||
};
|
||||
}, [storyId]);
|
||||
|
||||
useEffect(() => {
|
||||
const handleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
@@ -187,7 +255,6 @@ export function WorkItemDetailPanel({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Placeholder sections for future content */}
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
@@ -195,9 +262,80 @@ export function WorkItemDetailPanel({
|
||||
gap: "8px",
|
||||
}}
|
||||
>
|
||||
{/* Agent Logs section */}
|
||||
<div
|
||||
data-testid={
|
||||
agentInfo ? "agent-logs-section" : "placeholder-agent-logs"
|
||||
}
|
||||
style={{
|
||||
border: "1px solid #2a2a2a",
|
||||
borderRadius: "8px",
|
||||
padding: "10px 12px",
|
||||
background: "#161616",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "space-between",
|
||||
marginBottom: agentInfo ? "6px" : "4px",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
fontWeight: 600,
|
||||
fontSize: "0.8em",
|
||||
color: agentInfo ? "#888" : "#555",
|
||||
}}
|
||||
>
|
||||
Agent Logs
|
||||
</div>
|
||||
{agentInfo && agentStatus && (
|
||||
<div
|
||||
data-testid="agent-status-badge"
|
||||
style={{
|
||||
fontSize: "0.7em",
|
||||
color: STATUS_COLORS[agentStatus],
|
||||
fontWeight: 600,
|
||||
}}
|
||||
>
|
||||
{agentInfo.agent_name} — {agentStatus}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{agentInfo && agentLog.length > 0 ? (
|
||||
<div
|
||||
data-testid="agent-log-output"
|
||||
style={{
|
||||
fontSize: "0.75em",
|
||||
fontFamily: "monospace",
|
||||
color: "#ccc",
|
||||
whiteSpace: "pre-wrap",
|
||||
wordBreak: "break-word",
|
||||
lineHeight: "1.5",
|
||||
maxHeight: "200px",
|
||||
overflowY: "auto",
|
||||
}}
|
||||
>
|
||||
{agentLog.join("")}
|
||||
</div>
|
||||
) : agentInfo ? (
|
||||
<div style={{ fontSize: "0.75em", color: "#444" }}>
|
||||
{agentStatus === "running" || agentStatus === "pending"
|
||||
? "Waiting for output..."
|
||||
: "No output."}
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ fontSize: "0.75em", color: "#444" }}>
|
||||
Coming soon
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Placeholder sections for future content */}
|
||||
{(
|
||||
[
|
||||
{ id: "agent-logs", label: "Agent Logs" },
|
||||
{ id: "test-output", label: "Test Output" },
|
||||
{ id: "coverage", label: "Coverage" },
|
||||
] as { id: string; label: string }[]
|
||||
|
||||
Reference in New Issue
Block a user