diff --git a/site/src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.stories.tsx b/site/src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.stories.tsx index aa8df60931..458418ba31 100644 --- a/site/src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.stories.tsx +++ b/site/src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.stories.tsx @@ -64,6 +64,7 @@ const defaultArgs: Omit< "parsedMessages" > = { subagentTitles: new Map(), + isTurnActive: false, }; const meta: Meta = { @@ -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 diff --git a/site/src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.tsx b/site/src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.tsx index f080e111a1..7caff5e8ed 100644 --- a/site/src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.tsx +++ b/site/src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.tsx @@ -1009,6 +1009,11 @@ interface ConversationTimelineProps { mcpServers?: readonly TypesGen.MCPServerConfig[]; computerUseSubagentIds?: Set; 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( @@ -1022,6 +1027,7 @@ export const ConversationTimeline = memo( mcpServers, computerUseSubagentIds, showDesktopPreviews, + isTurnActive, }) => { if (parsedMessages.length === 0) { return null; @@ -1055,7 +1061,9 @@ export const ConversationTimeline = memo( lastAsstId = null; } } - if (lastAsstId != null) lastAssistantPerTurnIds.add(lastAsstId); + if (lastAsstId != null && !isTurnActive) { + lastAssistantPerTurnIds.add(lastAsstId); + } return parsedMessages.map(({ message, parsed }) => message.role === "user" ? ( = ({ }) => { 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 = ({ mcpServers={mcpServers} computerUseSubagentIds={computerUseSubagentIds} showDesktopPreviews={false} + isTurnActive={isTurnActive} />