story-kit: merge 337_story_web_ui_button_to_stop_an_agent_on_a_story
This commit is contained in:
@@ -798,6 +798,12 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
|||||||
setQueuedMessages([...queuedMessagesRef.current]);
|
setQueuedMessages([...queuedMessagesRef.current]);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleStopAgent = useCallback((storyId: string, agentName: string) => {
|
||||||
|
agentsApi.stopAgent(storyId, agentName).catch((err: unknown) => {
|
||||||
|
console.error("Failed to stop agent:", err);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className="chat-container"
|
className="chat-container"
|
||||||
@@ -1077,18 +1083,21 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
|||||||
items={pipeline.done ?? []}
|
items={pipeline.done ?? []}
|
||||||
costs={storyTokenCosts}
|
costs={storyTokenCosts}
|
||||||
onItemClick={(item) => setSelectedWorkItemId(item.story_id)}
|
onItemClick={(item) => setSelectedWorkItemId(item.story_id)}
|
||||||
|
onStopAgent={handleStopAgent}
|
||||||
/>
|
/>
|
||||||
<StagePanel
|
<StagePanel
|
||||||
title="To Merge"
|
title="To Merge"
|
||||||
items={pipeline.merge}
|
items={pipeline.merge}
|
||||||
costs={storyTokenCosts}
|
costs={storyTokenCosts}
|
||||||
onItemClick={(item) => setSelectedWorkItemId(item.story_id)}
|
onItemClick={(item) => setSelectedWorkItemId(item.story_id)}
|
||||||
|
onStopAgent={handleStopAgent}
|
||||||
/>
|
/>
|
||||||
<StagePanel
|
<StagePanel
|
||||||
title="QA"
|
title="QA"
|
||||||
items={pipeline.qa}
|
items={pipeline.qa}
|
||||||
costs={storyTokenCosts}
|
costs={storyTokenCosts}
|
||||||
onItemClick={(item) => setSelectedWorkItemId(item.story_id)}
|
onItemClick={(item) => setSelectedWorkItemId(item.story_id)}
|
||||||
|
onStopAgent={handleStopAgent}
|
||||||
/>
|
/>
|
||||||
<StagePanel
|
<StagePanel
|
||||||
title="Current"
|
title="Current"
|
||||||
@@ -1098,6 +1107,7 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
|||||||
agentRoster={agentRoster}
|
agentRoster={agentRoster}
|
||||||
busyAgentNames={busyAgentNames}
|
busyAgentNames={busyAgentNames}
|
||||||
onStartAgent={handleStartAgent}
|
onStartAgent={handleStartAgent}
|
||||||
|
onStopAgent={handleStopAgent}
|
||||||
/>
|
/>
|
||||||
<StagePanel
|
<StagePanel
|
||||||
title="Backlog"
|
title="Backlog"
|
||||||
@@ -1107,6 +1117,7 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
|||||||
agentRoster={agentRoster}
|
agentRoster={agentRoster}
|
||||||
busyAgentNames={busyAgentNames}
|
busyAgentNames={busyAgentNames}
|
||||||
onStartAgent={handleStartAgent}
|
onStartAgent={handleStartAgent}
|
||||||
|
onStopAgent={handleStopAgent}
|
||||||
/>
|
/>
|
||||||
<ServerLogsPanel logs={serverLogs} />
|
<ServerLogsPanel logs={serverLogs} />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ interface StagePanelProps {
|
|||||||
items: PipelineStageItem[];
|
items: PipelineStageItem[];
|
||||||
emptyMessage?: string;
|
emptyMessage?: string;
|
||||||
onItemClick?: (item: PipelineStageItem) => void;
|
onItemClick?: (item: PipelineStageItem) => void;
|
||||||
|
onStopAgent?: (storyId: string, agentName: string) => void;
|
||||||
/** Map of story_id → total_cost_usd for displaying cost badges. */
|
/** Map of story_id → total_cost_usd for displaying cost badges. */
|
||||||
costs?: Map<string, number>;
|
costs?: Map<string, number>;
|
||||||
/** Agent roster to populate the start agent dropdown. */
|
/** Agent roster to populate the start agent dropdown. */
|
||||||
@@ -56,9 +57,11 @@ interface StagePanelProps {
|
|||||||
function AgentLozenge({
|
function AgentLozenge({
|
||||||
agent,
|
agent,
|
||||||
storyId,
|
storyId,
|
||||||
|
onStop,
|
||||||
}: {
|
}: {
|
||||||
agent: AgentAssignment;
|
agent: AgentAssignment;
|
||||||
storyId: string;
|
storyId: string;
|
||||||
|
onStop?: () => void;
|
||||||
}) {
|
}) {
|
||||||
const { saveSlotRect, pendingFlyIns } = useLozengeFly();
|
const { saveSlotRect, pendingFlyIns } = useLozengeFly();
|
||||||
const lozengeRef = useRef<HTMLDivElement>(null);
|
const lozengeRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -128,6 +131,31 @@ function AgentLozenge({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{label}
|
{label}
|
||||||
|
{isRunning && onStop && (
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
data-testid={`stop-agent-${storyId}`}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
onStop();
|
||||||
|
}}
|
||||||
|
title="Stop agent"
|
||||||
|
style={{
|
||||||
|
marginLeft: "4px",
|
||||||
|
padding: "0 3px",
|
||||||
|
background: "transparent",
|
||||||
|
border: "none",
|
||||||
|
color,
|
||||||
|
cursor: "pointer",
|
||||||
|
fontSize: "0.9em",
|
||||||
|
lineHeight: 1,
|
||||||
|
opacity: 0.8,
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
■
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -224,6 +252,7 @@ export function StagePanel({
|
|||||||
items,
|
items,
|
||||||
emptyMessage = "Empty.",
|
emptyMessage = "Empty.",
|
||||||
onItemClick,
|
onItemClick,
|
||||||
|
onStopAgent,
|
||||||
costs,
|
costs,
|
||||||
agentRoster,
|
agentRoster,
|
||||||
busyAgentNames,
|
busyAgentNames,
|
||||||
@@ -391,7 +420,19 @@ export function StagePanel({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{item.agent && (
|
{item.agent && (
|
||||||
<AgentLozenge agent={item.agent} storyId={item.story_id} />
|
<AgentLozenge
|
||||||
|
agent={item.agent}
|
||||||
|
storyId={item.story_id}
|
||||||
|
onStop={
|
||||||
|
onStopAgent && item.agent.status === "running"
|
||||||
|
? () =>
|
||||||
|
onStopAgent(
|
||||||
|
item.story_id,
|
||||||
|
item.agent?.agent_name ?? "",
|
||||||
|
)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
{canStart && onStartAgent && (
|
{canStart && onStartAgent && (
|
||||||
<StartAgentControl
|
<StartAgentControl
|
||||||
|
|||||||
Reference in New Issue
Block a user