diff --git a/frontend/src/components/features/chat/chat-interface.tsx b/frontend/src/components/features/chat/chat-interface.tsx index 040cd8f522..91f07fc611 100644 --- a/frontend/src/components/features/chat/chat-interface.tsx +++ b/frontend/src/components/features/chat/chat-interface.tsx @@ -68,6 +68,7 @@ export function ChatInterface() { const conversationWebSocket = useConversationWebSocket(); const { send } = useSendMessage(); const storeEvents = useEventStore((state) => state.events); + const uiEvents = useEventStore((state) => state.uiEvents); const { setOptimisticUserMessage, getOptimisticUserMessage } = useOptimisticUserMessageStore(); const { t } = useTranslation(); @@ -121,11 +122,13 @@ export function ChatInterface() { .filter(isActionOrObservation) .filter(shouldRenderEvent); - // Filter V1 events - const v1Events = storeEvents.filter(isV1Event).filter(shouldRenderV1Event); + // Filter V1 events - use uiEvents for rendering (actions replaced by observations) + const v1UiEvents = uiEvents.filter(isV1Event).filter(shouldRenderV1Event); + // Keep full v1 events for lookups (includes both actions and observations) + const v1FullEvents = storeEvents.filter(isV1Event); // Combined events count for tracking - const totalEvents = v0Events.length || v1Events.length; + const totalEvents = v0Events.length || v1UiEvents.length; // Check if there are any substantive agent actions (not just system messages) const hasSubstantiveAgentActions = React.useMemo( @@ -223,7 +226,7 @@ export function ChatInterface() { }; const v0UserEventsExist = hasUserEvent(v0Events); - const v1UserEventsExist = hasV1UserEvent(v1Events); + const v1UserEventsExist = hasV1UserEvent(v1FullEvents); const userEventsExist = v0UserEventsExist || v1UserEventsExist; return ( @@ -267,7 +270,7 @@ export function ChatInterface() { )} {!conversationWebSocket?.isLoadingHistory && v1UserEventsExist && ( - + )} diff --git a/frontend/src/components/v1/chat/event-content-helpers/should-render-event.ts b/frontend/src/components/v1/chat/event-content-helpers/should-render-event.ts index 8acef2da03..a5fdc62252 100644 --- a/frontend/src/components/v1/chat/event-content-helpers/should-render-event.ts +++ b/frontend/src/components/v1/chat/event-content-helpers/should-render-event.ts @@ -7,17 +7,6 @@ import { isConversationStateUpdateEvent, } from "#/types/v1/type-guards"; -// V1 events that should not be rendered -const NO_RENDER_ACTION_TYPES = [ - "ThinkAction", - // Add more action types that should not be rendered -]; - -const NO_RENDER_OBSERVATION_TYPES = [ - "ThinkObservation", - // Add more observation types that should not be rendered -]; - export const shouldRenderEvent = (event: OpenHandsEvent) => { // Explicitly exclude system events that should not be rendered in chat if (isConversationStateUpdateEvent(event)) { @@ -34,18 +23,12 @@ export const shouldRenderEvent = (event: OpenHandsEvent) => { return false; } - return !NO_RENDER_ACTION_TYPES.includes(actionType); + return true; } - // Render observation events (with filtering) + // Render observation events if (isObservationEvent(event)) { - // For V1, observation is an object with kind property - const observationType = event.observation.kind; - - // Note: ObservationEvent source is always "environment", not "user" - // So no need to check for user source here - - return !NO_RENDER_OBSERVATION_TYPES.includes(observationType); + return true; } // Render message events (user and assistant messages) diff --git a/frontend/src/components/v1/chat/event-message-components/index.ts b/frontend/src/components/v1/chat/event-message-components/index.ts index 1f705a1f7a..3672255101 100644 --- a/frontend/src/components/v1/chat/event-message-components/index.ts +++ b/frontend/src/components/v1/chat/event-message-components/index.ts @@ -3,3 +3,4 @@ export { ObservationPairEventMessage } from "./observation-pair-event-message"; export { ErrorEventMessage } from "./error-event-message"; export { FinishEventMessage } from "./finish-event-message"; export { GenericEventMessageWrapper } from "./generic-event-message-wrapper"; +export { ThoughtEventMessage } from "./thought-event-message"; diff --git a/frontend/src/components/v1/chat/event-message-components/thought-event-message.tsx b/frontend/src/components/v1/chat/event-message-components/thought-event-message.tsx new file mode 100644 index 0000000000..9d6dfdcea1 --- /dev/null +++ b/frontend/src/components/v1/chat/event-message-components/thought-event-message.tsx @@ -0,0 +1,32 @@ +import React from "react"; +import { ActionEvent } from "#/types/v1/core"; +import { ChatMessage } from "../../../features/chat/chat-message"; + +interface ThoughtEventMessageProps { + event: ActionEvent; + actions?: Array<{ + icon: React.ReactNode; + onClick: () => void; + tooltip?: string; + }>; +} + +export function ThoughtEventMessage({ + event, + actions, +}: ThoughtEventMessageProps) { + // Extract thought content from the action event + const thoughtContent = event.thought + .filter((t) => t.type === "text") + .map((t) => t.text) + .join("\n"); + + // If there's no thought content, don't render anything + if (!thoughtContent) { + return null; + } + + return ( + + ); +} diff --git a/frontend/src/components/v1/chat/event-message.tsx b/frontend/src/components/v1/chat/event-message.tsx index cc5463f080..dbe327b31b 100644 --- a/frontend/src/components/v1/chat/event-message.tsx +++ b/frontend/src/components/v1/chat/event-message.tsx @@ -14,13 +14,13 @@ import { ErrorEventMessage, UserAssistantEventMessage, FinishEventMessage, - ObservationPairEventMessage, GenericEventMessageWrapper, + ThoughtEventMessage, } from "./event-message-components"; interface EventMessageProps { event: OpenHandsEvent; - hasObservationPair: boolean; + messages: OpenHandsEvent[]; isLastMessage: boolean; microagentStatus?: MicroagentStatus | null; microagentConversationId?: string; @@ -36,7 +36,7 @@ interface EventMessageProps { /* eslint-disable react/jsx-props-no-spreading */ export function EventMessage({ event, - hasObservationPair, + messages, isLastMessage, microagentStatus, microagentConversationId, @@ -69,19 +69,6 @@ export function EventMessage({ return ; } - // Observation pairs with actions - if (hasObservationPair && isActionEvent(event)) { - return ( - - ); - } - // Finish actions if (isActionEvent(event) && event.action.kind === "FinishAction") { return ( @@ -92,6 +79,39 @@ export function EventMessage({ ); } + // Action events - render thought + action (will be replaced by thought + observation) + if (isActionEvent(event)) { + return ( + <> + + + + ); + } + + // Observation events - find the corresponding action and render thought + observation + if (isObservationEvent(event)) { + // Find the action that this observation is responding to + const correspondingAction = messages.find( + (msg) => isActionEvent(msg) && msg.id === event.action_id, + ); + + return ( + <> + {correspondingAction && isActionEvent(correspondingAction) && ( + + )} + + + ); + } + // Message events (user and assistant messages) if (!isActionEvent(event) && !isObservationEvent(event)) { // This is a MessageEvent @@ -104,7 +124,7 @@ export function EventMessage({ ); } - // Generic fallback for all other events (including observation events) + // Generic fallback for all other events return ( ); diff --git a/frontend/src/components/v1/chat/messages.tsx b/frontend/src/components/v1/chat/messages.tsx index d6cc018090..4c4b733e76 100644 --- a/frontend/src/components/v1/chat/messages.tsx +++ b/frontend/src/components/v1/chat/messages.tsx @@ -1,6 +1,5 @@ import React from "react"; import { OpenHandsEvent } from "#/types/v1/core"; -import { isActionEvent, isObservationEvent } from "#/types/v1/type-guards"; import { EventMessage } from "./event-message"; import { ChatMessage } from "../../features/chat/chat-message"; import { useOptimisticUserMessageStore } from "#/stores/optimistic-user-message-store"; @@ -9,29 +8,16 @@ import { useOptimisticUserMessageStore } from "#/stores/optimistic-user-message- // import MemoryIcon from "#/icons/memory_icon.svg?react"; interface MessagesProps { - messages: OpenHandsEvent[]; + messages: OpenHandsEvent[]; // UI events (actions replaced by observations) + allEvents: OpenHandsEvent[]; // Full event history (for action lookup) } export const Messages: React.FC = React.memo( - ({ messages }) => { + ({ messages, allEvents }) => { const { getOptimisticUserMessage } = useOptimisticUserMessageStore(); const optimisticUserMessage = getOptimisticUserMessage(); - const actionHasObservationPair = React.useCallback( - (event: OpenHandsEvent): boolean => { - if (isActionEvent(event)) { - // Check if there's a corresponding observation event - return !!messages.some( - (msg) => isObservationEvent(msg) && msg.action_id === event.id, - ); - } - - return false; - }, - [messages], - ); - // TODO: Implement microagent functionality for V1 if needed // For now, we'll skip microagent features @@ -41,7 +27,7 @@ export const Messages: React.FC = React.memo(