From a734cd36c604f36eb85dfcc545c1b8e676828483 Mon Sep 17 00:00:00 2001 From: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com> Date: Wed, 27 May 2026 15:15:20 +0000 Subject: [PATCH] feat: mark goal-sent chat messages --- .../ConversationTimeline.stories.tsx | 38 +++ .../ChatConversation/ConversationTimeline.tsx | 223 ++++++++++-------- 2 files changed, 165 insertions(+), 96 deletions(-) diff --git a/site/src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.stories.tsx b/site/src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.stories.tsx index 0d004362de..4ad5f75ea2 100644 --- a/site/src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.stories.tsx +++ b/site/src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.stories.tsx @@ -218,17 +218,20 @@ const buildUserMessage = ({ text, files = [], createdAt = baseMessage.created_at, + sentAsGoal = false, }: { id?: number; text?: string; files?: TypesGen.ChatFilePart[]; createdAt?: string; + sentAsGoal?: boolean; }): TypesGen.ChatMessage => ({ ...baseMessage, created_at: createdAt, id, role: "user", content: [...(text ? [buildTextPart(text)] : []), ...files], + ...(sentAsGoal ? { sent_as_goal: true } : {}), }); const buildStoryArgs = (...messages: TypesGen.ChatMessage[]) => ({ @@ -1041,6 +1044,41 @@ export const UserMessageTextOnly: Story = { }, }; +/** Goal-sent user messages show a durable transcript marker. */ +export const UserMessageSentAsGoalMarker: Story = { + args: buildStoryArgs( + buildUserMessage({ + id: 1, + text: "Use this screenshot as the goal", + files: [buildInlineAttachmentPart("image/png", TEST_PNG_B64)], + sentAsGoal: true, + }), + { + ...baseMessage, + id: 2, + role: "assistant", + content: [{ type: "text", text: "I will pursue that goal." }], + }, + ), + play: async ({ canvasElement }) => { + const canvas = within(canvasElement); + const marker = canvas.getByTestId("sent-as-goal-marker"); + expect(marker).toBeVisible(); + expect(marker).toHaveTextContent("Sent as goal"); + expect(canvas.getAllByTestId("sent-as-goal-marker")).toHaveLength(1); + + const markerActionRow = marker.closest('[data-testid="message-actions"]'); + if (!(markerActionRow instanceof HTMLElement)) { + throw new Error("Sent as goal marker action row not found"); + } + expect( + within(markerActionRow).queryByRole("button", { + name: "Copy message", + }), + ).not.toBeInTheDocument(); + }, +}; + /** Assistant-side images go through BlockList, not the user path. */ export const AssistantMessageWithImage: Story = { args: { diff --git a/site/src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.tsx b/site/src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.tsx index 83024550f3..02da8401ce 100644 --- a/site/src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.tsx +++ b/site/src/pages/AgentsPage/components/ChatConversation/ConversationTimeline.tsx @@ -1,4 +1,9 @@ -import { ChevronLeftIcon, ChevronRightIcon, PencilIcon } from "lucide-react"; +import { + ChevronLeftIcon, + ChevronRightIcon, + PencilIcon, + TargetIcon, +} from "lucide-react"; import { type FC, Fragment, @@ -80,6 +85,16 @@ const getChatMessageTextContent = ( return textContent.length > 0 ? textContent : undefined; }; +const SentAsGoalMarker: FC = () => ( +
+ + Sent as goal +
+); + const ReasoningDisclosure = memo<{ id: string; text: string; @@ -589,6 +604,19 @@ const ChatMessageItem = memo<{ hasActiveStream, isAwaitingFirstStreamChunk, }); + const hasUserMessageJumpControls = Boolean( + isUser && + onJumpToUserMessage && + (prevUserMessageId !== undefined || nextUserMessageId !== undefined), + ); + const hasMessageControls = Boolean( + displayState.hasCopyableContent || + (isUser && onEditUserMessage) || + hasUserMessageJumpControls, + ); + const showsSentAsGoalMarker = isUser && message.sent_as_goal === true; + const showsMessageActionRow = + !hideActions && (hasMessageControls || showsSentAsGoalMarker); if (displayState.shouldHide) { return null; } @@ -648,102 +676,105 @@ const ChatMessageItem = memo<{ )} - {!hideActions && - (displayState.hasCopyableContent || - (isUser && onEditUserMessage)) && ( -
- {displayState.hasCopyableContent && ( - - )} - {isUser && onEditUserMessage && ( - - - - - Edit message - - )} - {isUser && - onJumpToUserMessage && - (prevUserMessageId !== undefined || - nextUserMessageId !== undefined) && ( - <> - - - - - - Jump to previous user message - - - - - - - - Jump to next user message - - - + {showsMessageActionRow && ( +
+ {hasMessageControls && ( +
+ {displayState.hasCopyableContent && ( + )} -
- )} + {isUser && onEditUserMessage && ( + + + + + Edit message + + )} + {isUser && + onJumpToUserMessage && + (prevUserMessageId !== undefined || + nextUserMessageId !== undefined) && ( + <> + + + + + + Jump to previous user message + + + + + + + + Jump to next user message + + + + )} +
+ )} + {showsSentAsGoalMarker && } +
+ )} {displayState.needsAssistantBottomSpacer && (
)}