mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
fix(site/src/pages/AgentsPage): hide copy button during active turn (#23962)
The copy button on the last assistant message was showing even while the turn was still in progress (agent streaming or running tool calls). The content is not final at that point, so the button should be suppressed until the turn completes. The `lastAssistantPerTurnIds` computation unconditionally included the trailing assistant message. Now it checks a new `isTurnActive` prop derived from `isActiveChatStatus(chatStatus) || hasStreamState` and skips the trailing ID when the turn is active. Completed turns (those followed by a user message) are unaffected.
This commit is contained in:
+33
@@ -64,6 +64,7 @@ const defaultArgs: Omit<
|
||||
"parsedMessages"
|
||||
> = {
|
||||
subagentTitles: new Map(),
|
||||
isTurnActive: false,
|
||||
};
|
||||
|
||||
const meta: Meta<typeof ConversationTimeline> = {
|
||||
@@ -766,6 +767,38 @@ export const CopyButtonWritesToClipboard: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* When the turn is still active (isTurnActive=true), the trailing
|
||||
* assistant message should NOT show a copy button because the
|
||||
* content is not yet final.
|
||||
*/
|
||||
export const NoCopyButtonDuringActiveTurn: Story = {
|
||||
args: {
|
||||
...defaultArgs,
|
||||
isTurnActive: true,
|
||||
parsedMessages: buildMessages([
|
||||
{
|
||||
...baseMessage,
|
||||
id: 1,
|
||||
role: "user",
|
||||
content: [{ type: "text", text: "Fix the bug" }],
|
||||
},
|
||||
{
|
||||
...baseMessage,
|
||||
id: 2,
|
||||
role: "assistant",
|
||||
content: [{ type: "text", text: "Let me look at the code." }],
|
||||
},
|
||||
]),
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
expect(
|
||||
canvas.queryByTestId("assistant-copy-button"),
|
||||
).not.toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Regression: copy button appears only on the last assistant message
|
||||
* in a turn that includes tool calls. The isLastAssistantMessage
|
||||
|
||||
@@ -1009,6 +1009,11 @@ interface ConversationTimelineProps {
|
||||
mcpServers?: readonly TypesGen.MCPServerConfig[];
|
||||
computerUseSubagentIds?: Set<string>;
|
||||
showDesktopPreviews?: boolean;
|
||||
// When true the current turn is still in progress (the agent
|
||||
// is streaming or a tool call is running). The copy button on
|
||||
// the trailing assistant message is suppressed because the
|
||||
// content is not yet final.
|
||||
isTurnActive: boolean;
|
||||
}
|
||||
|
||||
export const ConversationTimeline = memo<ConversationTimelineProps>(
|
||||
@@ -1022,6 +1027,7 @@ export const ConversationTimeline = memo<ConversationTimelineProps>(
|
||||
mcpServers,
|
||||
computerUseSubagentIds,
|
||||
showDesktopPreviews,
|
||||
isTurnActive,
|
||||
}) => {
|
||||
if (parsedMessages.length === 0) {
|
||||
return null;
|
||||
@@ -1055,7 +1061,9 @@ export const ConversationTimeline = memo<ConversationTimelineProps>(
|
||||
lastAsstId = null;
|
||||
}
|
||||
}
|
||||
if (lastAsstId != null) lastAssistantPerTurnIds.add(lastAsstId);
|
||||
if (lastAsstId != null && !isTurnActive) {
|
||||
lastAssistantPerTurnIds.add(lastAsstId);
|
||||
}
|
||||
return parsedMessages.map(({ message, parsed }) =>
|
||||
message.role === "user" ? (
|
||||
<StickyUserMessage
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { ConversationTimeline } from "./ChatConversation/ConversationTimeline";
|
||||
import { getLatestContextUsage } from "./ChatConversation/chatHelpers";
|
||||
import {
|
||||
isActiveChatStatus,
|
||||
selectChatStatus,
|
||||
selectHasStreamState,
|
||||
selectMessagesByID,
|
||||
@@ -64,6 +65,9 @@ export const ChatPageTimeline: FC<ChatPageTimelineProps> = ({
|
||||
}) => {
|
||||
const messagesByID = useChatSelector(store, selectMessagesByID);
|
||||
const orderedMessageIDs = useChatSelector(store, selectOrderedMessageIDs);
|
||||
const chatStatus = useChatSelector(store, selectChatStatus);
|
||||
const hasStreamState = useChatSelector(store, selectHasStreamState);
|
||||
const isTurnActive = isActiveChatStatus(chatStatus) || hasStreamState;
|
||||
|
||||
const messages = orderedMessageIDs
|
||||
.map((messageID) => messagesByID.get(messageID))
|
||||
@@ -91,6 +95,7 @@ export const ChatPageTimeline: FC<ChatPageTimelineProps> = ({
|
||||
mcpServers={mcpServers}
|
||||
computerUseSubagentIds={computerUseSubagentIds}
|
||||
showDesktopPreviews={false}
|
||||
isTurnActive={isTurnActive}
|
||||
/>
|
||||
<LiveStreamTail
|
||||
store={store}
|
||||
|
||||
Reference in New Issue
Block a user