perf: avoid per-message review metadata checks
Compute review transfer state once per chat render Hide transfer actions when linked review sessions are inactive Remove session-list scans from individual message rows
This commit is contained in:
@@ -31,6 +31,7 @@ import { copyTextToClipboard } from '@/lib/clipboard';
|
|||||||
import { FadeInOnReveal } from './message/FadeInOnReveal';
|
import { FadeInOnReveal } from './message/FadeInOnReveal';
|
||||||
import { streamPerfCount } from '@/stores/utils/streamDebug';
|
import { streamPerfCount } from '@/stores/utils/streamDebug';
|
||||||
import { areOptionalRenderRelevantMessagesEqual, areRenderRelevantMessagesEqual, areRelevantTurnGroupingContextsEqual } from './message/renderCompare';
|
import { areOptionalRenderRelevantMessagesEqual, areRenderRelevantMessagesEqual, areRelevantTurnGroupingContextsEqual } from './message/renderCompare';
|
||||||
|
import type { ReviewTransferDirection } from '@/lib/reviewFlow';
|
||||||
|
|
||||||
const ToolOutputDialog = lazyWithChunkRecovery(() => import('./message/ToolOutputDialog'));
|
const ToolOutputDialog = lazyWithChunkRecovery(() => import('./message/ToolOutputDialog'));
|
||||||
|
|
||||||
@@ -133,6 +134,7 @@ interface ChatMessageProps {
|
|||||||
activeStreamingPhase?: StreamPhase | null;
|
activeStreamingPhase?: StreamPhase | null;
|
||||||
animateUserOnMount?: boolean;
|
animateUserOnMount?: boolean;
|
||||||
onUserAnimationConsumed?: (messageId: string) => void;
|
onUserAnimationConsumed?: (messageId: string) => void;
|
||||||
|
reviewTransferDirection?: ReviewTransferDirection | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const ChatMessage: React.FC<ChatMessageProps> = ({
|
const ChatMessage: React.FC<ChatMessageProps> = ({
|
||||||
@@ -147,6 +149,7 @@ const ChatMessage: React.FC<ChatMessageProps> = ({
|
|||||||
activeStreamingPhase = null,
|
activeStreamingPhase = null,
|
||||||
animateUserOnMount = false,
|
animateUserOnMount = false,
|
||||||
onUserAnimationConsumed,
|
onUserAnimationConsumed,
|
||||||
|
reviewTransferDirection = null,
|
||||||
}) => {
|
}) => {
|
||||||
const { isMobile, isTablet, hasTouchInput } = useDeviceInfo();
|
const { isMobile, isTablet, hasTouchInput } = useDeviceInfo();
|
||||||
const alwaysShowMessageActions = isMobile || isTablet;
|
const alwaysShowMessageActions = isMobile || isTablet;
|
||||||
@@ -1138,6 +1141,7 @@ const ChatMessage: React.FC<ChatMessageProps> = ({
|
|||||||
turnGroupingContext={turnGroupingContext}
|
turnGroupingContext={turnGroupingContext}
|
||||||
errorMessage={assistantErrorText}
|
errorMessage={assistantErrorText}
|
||||||
errorVariant={assistantErrorVariant}
|
errorVariant={assistantErrorVariant}
|
||||||
|
reviewTransferDirection={reviewTransferDirection}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -1171,6 +1175,7 @@ export default React.memo(ChatMessage, (prev, next) => {
|
|||||||
)
|
)
|
||||||
&& prev.isInActiveTurn === next.isInActiveTurn
|
&& prev.isInActiveTurn === next.isInActiveTurn
|
||||||
&& prev.activeStreamingPhase === next.activeStreamingPhase
|
&& prev.activeStreamingPhase === next.activeStreamingPhase
|
||||||
|
&& prev.reviewTransferDirection === next.reviewTransferDirection
|
||||||
&& prev.assistantHeaderMessageId === next.assistantHeaderMessageId
|
&& prev.assistantHeaderMessageId === next.assistantHeaderMessageId
|
||||||
&& prev.animateUserOnMount === next.animateUserOnMount
|
&& prev.animateUserOnMount === next.animateUserOnMount
|
||||||
&& prev.onUserAnimationConsumed === next.onUserAnimationConsumed
|
&& prev.onUserAnimationConsumed === next.onUserAnimationConsumed
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ import { hasPendingUserSendAnimation, consumePendingUserSendAnimation } from '@/
|
|||||||
import { streamPerfCount, streamPerfMeasure } from '@/stores/utils/streamDebug';
|
import { streamPerfCount, streamPerfMeasure } from '@/stores/utils/streamDebug';
|
||||||
import type { StreamPhase } from './message/types';
|
import type { StreamPhase } from './message/types';
|
||||||
import { normalizeParts } from './message/partUtils';
|
import { normalizeParts } from './message/partUtils';
|
||||||
|
import { useGlobalSessionsStore } from '@/stores/useGlobalSessionsStore';
|
||||||
|
import { getReviewTransferDirection, type ReviewTransferDirection } from '@/lib/reviewFlow';
|
||||||
|
import { getOriginalSessionID, getReviewSessionID } from '@/lib/sessionReviewMetadata';
|
||||||
|
|
||||||
const MESSAGE_LIST_VIRTUALIZE_THRESHOLD = 5;
|
const MESSAGE_LIST_VIRTUALIZE_THRESHOLD = 5;
|
||||||
const MESSAGE_LIST_OVERSCAN = 6;
|
const MESSAGE_LIST_OVERSCAN = 6;
|
||||||
@@ -444,6 +447,7 @@ interface MessageRowProps {
|
|||||||
onContentChange: (reason?: ContentChangeReason) => void;
|
onContentChange: (reason?: ContentChangeReason) => void;
|
||||||
animationHandlers: AnimationHandlers;
|
animationHandlers: AnimationHandlers;
|
||||||
scrollToBottom?: () => void;
|
scrollToBottom?: () => void;
|
||||||
|
reviewTransferDirection?: ReviewTransferDirection | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MessageRow = React.memo<MessageRowProps>(({
|
const MessageRow = React.memo<MessageRowProps>(({
|
||||||
@@ -459,6 +463,7 @@ const MessageRow = React.memo<MessageRowProps>(({
|
|||||||
onContentChange,
|
onContentChange,
|
||||||
animationHandlers,
|
animationHandlers,
|
||||||
scrollToBottom,
|
scrollToBottom,
|
||||||
|
reviewTransferDirection,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<ChatMessage
|
<ChatMessage
|
||||||
@@ -474,6 +479,7 @@ const MessageRow = React.memo<MessageRowProps>(({
|
|||||||
assistantHeaderMessageId={assistantHeaderMessageId}
|
assistantHeaderMessageId={assistantHeaderMessageId}
|
||||||
isInActiveTurn={isInActiveTurn}
|
isInActiveTurn={isInActiveTurn}
|
||||||
activeStreamingPhase={activeStreamingPhase}
|
activeStreamingPhase={activeStreamingPhase}
|
||||||
|
reviewTransferDirection={reviewTransferDirection}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}, (prev, next) => {
|
}, (prev, next) => {
|
||||||
@@ -491,6 +497,7 @@ const MessageRow = React.memo<MessageRowProps>(({
|
|||||||
&& prev.assistantHeaderMessageId === next.assistantHeaderMessageId
|
&& prev.assistantHeaderMessageId === next.assistantHeaderMessageId
|
||||||
&& prev.isInActiveTurn === next.isInActiveTurn
|
&& prev.isInActiveTurn === next.isInActiveTurn
|
||||||
&& prev.activeStreamingPhase === next.activeStreamingPhase
|
&& prev.activeStreamingPhase === next.activeStreamingPhase
|
||||||
|
&& prev.reviewTransferDirection === next.reviewTransferDirection
|
||||||
&& prev.animationHandlers?.onChunk === next.animationHandlers?.onChunk
|
&& prev.animationHandlers?.onChunk === next.animationHandlers?.onChunk
|
||||||
&& prev.animationHandlers?.onComplete === next.animationHandlers?.onComplete
|
&& prev.animationHandlers?.onComplete === next.animationHandlers?.onComplete
|
||||||
&& prev.animationHandlers?.onStreamingCandidate === next.animationHandlers?.onStreamingCandidate
|
&& prev.animationHandlers?.onStreamingCandidate === next.animationHandlers?.onStreamingCandidate
|
||||||
@@ -518,6 +525,7 @@ interface TurnBlockProps {
|
|||||||
onUserAnimationConsumed: (messageId: string) => void;
|
onUserAnimationConsumed: (messageId: string) => void;
|
||||||
activeStreamingMessageId?: string | null;
|
activeStreamingMessageId?: string | null;
|
||||||
activeStreamingPhase?: StreamPhase | null;
|
activeStreamingPhase?: StreamPhase | null;
|
||||||
|
reviewTransferDirection?: ReviewTransferDirection | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TurnBlock = React.memo(({
|
const TurnBlock = React.memo(({
|
||||||
@@ -536,6 +544,7 @@ const TurnBlock = React.memo(({
|
|||||||
onUserAnimationConsumed,
|
onUserAnimationConsumed,
|
||||||
activeStreamingMessageId,
|
activeStreamingMessageId,
|
||||||
activeStreamingPhase,
|
activeStreamingPhase,
|
||||||
|
reviewTransferDirection,
|
||||||
}: TurnBlockProps) => {
|
}: TurnBlockProps) => {
|
||||||
const turnUiState = turnUiStates.get(turn.turnId) ?? { isExpanded: defaultActivityExpanded };
|
const turnUiState = turnUiStates.get(turn.turnId) ?? { isExpanded: defaultActivityExpanded };
|
||||||
const handleToggleTurnGroup = React.useCallback(() => {
|
const handleToggleTurnGroup = React.useCallback(() => {
|
||||||
@@ -756,6 +765,7 @@ const TurnBlock = React.memo(({
|
|||||||
assistantHeaderMessageId={assistantHeaderMessageId}
|
assistantHeaderMessageId={assistantHeaderMessageId}
|
||||||
isInActiveTurn={Boolean(streamingAssistantMessageId) && message.info.id === streamingAssistantMessageId}
|
isInActiveTurn={Boolean(streamingAssistantMessageId) && message.info.id === streamingAssistantMessageId}
|
||||||
activeStreamingPhase={message.info.id === streamingAssistantMessageId ? activeStreamingPhase : null}
|
activeStreamingPhase={message.info.id === streamingAssistantMessageId ? activeStreamingPhase : null}
|
||||||
|
reviewTransferDirection={reviewTransferDirection}
|
||||||
animateUserOnMount={shouldAnimateUserMessage(message)}
|
animateUserOnMount={shouldAnimateUserMessage(message)}
|
||||||
onUserAnimationConsumed={onUserAnimationConsumed}
|
onUserAnimationConsumed={onUserAnimationConsumed}
|
||||||
onContentChange={onMessageContentChange}
|
onContentChange={onMessageContentChange}
|
||||||
@@ -782,6 +792,7 @@ const TurnBlock = React.memo(({
|
|||||||
turnGroupingContextBase,
|
turnGroupingContextBase,
|
||||||
streamingAssistantMessageId,
|
streamingAssistantMessageId,
|
||||||
activeStreamingPhase,
|
activeStreamingPhase,
|
||||||
|
reviewTransferDirection,
|
||||||
visibleAssistantMessages,
|
visibleAssistantMessages,
|
||||||
visibleAssistantIds,
|
visibleAssistantIds,
|
||||||
visibleActivitySegments,
|
visibleActivitySegments,
|
||||||
@@ -820,6 +831,7 @@ interface UngroupedMessageRowProps {
|
|||||||
onUserAnimationConsumed: (messageId: string) => void;
|
onUserAnimationConsumed: (messageId: string) => void;
|
||||||
activeStreamingMessageId?: string | null;
|
activeStreamingMessageId?: string | null;
|
||||||
activeStreamingPhase?: StreamPhase | null;
|
activeStreamingPhase?: StreamPhase | null;
|
||||||
|
reviewTransferDirection?: ReviewTransferDirection | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UngroupedMessageRow = React.memo(({
|
const UngroupedMessageRow = React.memo(({
|
||||||
@@ -833,6 +845,7 @@ const UngroupedMessageRow = React.memo(({
|
|||||||
onUserAnimationConsumed,
|
onUserAnimationConsumed,
|
||||||
activeStreamingMessageId,
|
activeStreamingMessageId,
|
||||||
activeStreamingPhase,
|
activeStreamingPhase,
|
||||||
|
reviewTransferDirection,
|
||||||
}: UngroupedMessageRowProps) => {
|
}: UngroupedMessageRowProps) => {
|
||||||
return (
|
return (
|
||||||
<MessageRow
|
<MessageRow
|
||||||
@@ -846,6 +859,7 @@ const UngroupedMessageRow = React.memo(({
|
|||||||
scrollToBottom={scrollToBottom}
|
scrollToBottom={scrollToBottom}
|
||||||
isInActiveTurn={Boolean(activeStreamingMessageId) && message.info.id === activeStreamingMessageId}
|
isInActiveTurn={Boolean(activeStreamingMessageId) && message.info.id === activeStreamingMessageId}
|
||||||
activeStreamingPhase={message.info.id === activeStreamingMessageId ? activeStreamingPhase : null}
|
activeStreamingPhase={message.info.id === activeStreamingMessageId ? activeStreamingPhase : null}
|
||||||
|
reviewTransferDirection={reviewTransferDirection}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -867,6 +881,7 @@ interface MessageListEntryProps {
|
|||||||
onUserAnimationConsumed: (messageId: string) => void;
|
onUserAnimationConsumed: (messageId: string) => void;
|
||||||
activeStreamingMessageId?: string | null;
|
activeStreamingMessageId?: string | null;
|
||||||
activeStreamingPhase?: StreamPhase | null;
|
activeStreamingPhase?: StreamPhase | null;
|
||||||
|
reviewTransferDirection?: ReviewTransferDirection | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const turnContainsMessageId = (turn: TurnRecord, messageId: string | null | undefined): boolean => {
|
const turnContainsMessageId = (turn: TurnRecord, messageId: string | null | undefined): boolean => {
|
||||||
@@ -896,6 +911,7 @@ const MessageListEntry = React.memo(({
|
|||||||
onUserAnimationConsumed,
|
onUserAnimationConsumed,
|
||||||
activeStreamingMessageId,
|
activeStreamingMessageId,
|
||||||
activeStreamingPhase,
|
activeStreamingPhase,
|
||||||
|
reviewTransferDirection,
|
||||||
}: MessageListEntryProps) => {
|
}: MessageListEntryProps) => {
|
||||||
if (entry.kind === 'ungrouped') {
|
if (entry.kind === 'ungrouped') {
|
||||||
return (
|
return (
|
||||||
@@ -910,6 +926,7 @@ const MessageListEntry = React.memo(({
|
|||||||
onUserAnimationConsumed={onUserAnimationConsumed}
|
onUserAnimationConsumed={onUserAnimationConsumed}
|
||||||
activeStreamingMessageId={activeStreamingMessageId}
|
activeStreamingMessageId={activeStreamingMessageId}
|
||||||
activeStreamingPhase={activeStreamingPhase}
|
activeStreamingPhase={activeStreamingPhase}
|
||||||
|
reviewTransferDirection={reviewTransferDirection}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -927,6 +944,7 @@ const MessageListEntry = React.memo(({
|
|||||||
onUserAnimationConsumed={onUserAnimationConsumed}
|
onUserAnimationConsumed={onUserAnimationConsumed}
|
||||||
activeStreamingMessageId={activeStreamingMessageId}
|
activeStreamingMessageId={activeStreamingMessageId}
|
||||||
activeStreamingPhase={activeStreamingPhase}
|
activeStreamingPhase={activeStreamingPhase}
|
||||||
|
reviewTransferDirection={reviewTransferDirection}
|
||||||
onMessageContentChange={onMessageContentChange}
|
onMessageContentChange={onMessageContentChange}
|
||||||
getAnimationHandlers={getAnimationHandlers}
|
getAnimationHandlers={getAnimationHandlers}
|
||||||
scrollToBottom={scrollToBottom}
|
scrollToBottom={scrollToBottom}
|
||||||
@@ -956,9 +974,10 @@ type StaticHistoryListProps = {
|
|||||||
shouldAnimateUserMessage: (message: ChatMessageEntry) => boolean;
|
shouldAnimateUserMessage: (message: ChatMessageEntry) => boolean;
|
||||||
onUserAnimationConsumed: (messageId: string) => void;
|
onUserAnimationConsumed: (messageId: string) => void;
|
||||||
activeStreamingPhase?: StreamPhase | null;
|
activeStreamingPhase?: StreamPhase | null;
|
||||||
|
reviewTransferDirection?: ReviewTransferDirection | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const StaticHistoryList = React.memo(({ entries, shouldVirtualize, virtualRows, totalSize, measureElement, contentRef, onMessageContentChange, getAnimationHandlers, scrollToBottom, stickyUserHeader, defaultActivityExpanded, turnUiStates, onToggleTurnGroup, chatRenderMode, shouldAnimateUserMessage, onUserAnimationConsumed, activeStreamingPhase }: StaticHistoryListProps) => {
|
const StaticHistoryList = React.memo(({ entries, shouldVirtualize, virtualRows, totalSize, measureElement, contentRef, onMessageContentChange, getAnimationHandlers, scrollToBottom, stickyUserHeader, defaultActivityExpanded, turnUiStates, onToggleTurnGroup, chatRenderMode, shouldAnimateUserMessage, onUserAnimationConsumed, activeStreamingPhase, reviewTransferDirection }: StaticHistoryListProps) => {
|
||||||
const renderEntry = React.useCallback((entry: RenderEntry) => {
|
const renderEntry = React.useCallback((entry: RenderEntry) => {
|
||||||
return (
|
return (
|
||||||
<MessageListEntry
|
<MessageListEntry
|
||||||
@@ -977,9 +996,10 @@ const StaticHistoryList = React.memo(({ entries, shouldVirtualize, virtualRows,
|
|||||||
onUserAnimationConsumed={onUserAnimationConsumed}
|
onUserAnimationConsumed={onUserAnimationConsumed}
|
||||||
activeStreamingMessageId={null}
|
activeStreamingMessageId={null}
|
||||||
activeStreamingPhase={activeStreamingPhase}
|
activeStreamingPhase={activeStreamingPhase}
|
||||||
|
reviewTransferDirection={reviewTransferDirection}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}, [activeStreamingPhase, chatRenderMode, defaultActivityExpanded, getAnimationHandlers, onMessageContentChange, onToggleTurnGroup, onUserAnimationConsumed, scrollToBottom, shouldAnimateUserMessage, stickyUserHeader, turnUiStates]);
|
}, [activeStreamingPhase, chatRenderMode, defaultActivityExpanded, getAnimationHandlers, onMessageContentChange, onToggleTurnGroup, onUserAnimationConsumed, reviewTransferDirection, scrollToBottom, shouldAnimateUserMessage, stickyUserHeader, turnUiStates]);
|
||||||
|
|
||||||
const paddingTop = shouldVirtualize && virtualRows.length > 0
|
const paddingTop = shouldVirtualize && virtualRows.length > 0
|
||||||
? virtualRows[0]?.start ?? 0
|
? virtualRows[0]?.start ?? 0
|
||||||
@@ -1060,6 +1080,7 @@ const StreamingTailContent: React.FC<{
|
|||||||
onUserAnimationConsumed: (messageId: string) => void;
|
onUserAnimationConsumed: (messageId: string) => void;
|
||||||
activeStreamingMessageId?: string | null;
|
activeStreamingMessageId?: string | null;
|
||||||
activeStreamingPhase?: StreamPhase | null;
|
activeStreamingPhase?: StreamPhase | null;
|
||||||
|
reviewTransferDirection?: ReviewTransferDirection | null;
|
||||||
}> = ({
|
}> = ({
|
||||||
entry,
|
entry,
|
||||||
onMessageContentChange,
|
onMessageContentChange,
|
||||||
@@ -1075,6 +1096,7 @@ const StreamingTailContent: React.FC<{
|
|||||||
onUserAnimationConsumed,
|
onUserAnimationConsumed,
|
||||||
activeStreamingMessageId,
|
activeStreamingMessageId,
|
||||||
activeStreamingPhase,
|
activeStreamingPhase,
|
||||||
|
reviewTransferDirection,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<MessageListEntry
|
<MessageListEntry
|
||||||
@@ -1092,6 +1114,7 @@ const StreamingTailContent: React.FC<{
|
|||||||
onUserAnimationConsumed={onUserAnimationConsumed}
|
onUserAnimationConsumed={onUserAnimationConsumed}
|
||||||
activeStreamingMessageId={activeStreamingMessageId}
|
activeStreamingMessageId={activeStreamingMessageId}
|
||||||
activeStreamingPhase={activeStreamingPhase}
|
activeStreamingPhase={activeStreamingPhase}
|
||||||
|
reviewTransferDirection={reviewTransferDirection}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -1118,6 +1141,20 @@ const MessageList = React.forwardRef<MessageListHandle, MessageListProps>(({
|
|||||||
const activityRenderMode = useUIStore((state) => state.activityRenderMode);
|
const activityRenderMode = useUIStore((state) => state.activityRenderMode);
|
||||||
const showTurnChangedFiles = useUIStore((state) => state.showTurnChangedFiles);
|
const showTurnChangedFiles = useUIStore((state) => state.showTurnChangedFiles);
|
||||||
const defaultActivityExpanded = activityRenderMode === 'summary';
|
const defaultActivityExpanded = activityRenderMode === 'summary';
|
||||||
|
const reviewTransferDirection = useGlobalSessionsStore((state) => {
|
||||||
|
const currentSession = state.activeSessions.find((session) => session.id === sessionKey);
|
||||||
|
const direction = getReviewTransferDirection(currentSession);
|
||||||
|
if (!currentSession || !direction) return null;
|
||||||
|
|
||||||
|
const targetSessionId = direction === 'review-to-original'
|
||||||
|
? getOriginalSessionID(currentSession)
|
||||||
|
: getReviewSessionID(currentSession);
|
||||||
|
if (!targetSessionId) return null;
|
||||||
|
|
||||||
|
return state.activeSessions.some((session) => session.id === targetSessionId)
|
||||||
|
? direction
|
||||||
|
: null;
|
||||||
|
});
|
||||||
const [turnUiStates, setTurnUiStates] = React.useState<Map<string, TurnUiState>>(() => new Map());
|
const [turnUiStates, setTurnUiStates] = React.useState<Map<string, TurnUiState>>(() => new Map());
|
||||||
const userAnimationRef = React.useRef<{
|
const userAnimationRef = React.useRef<{
|
||||||
sessionKey: string | undefined;
|
sessionKey: string | undefined;
|
||||||
@@ -1692,6 +1729,7 @@ const MessageList = React.forwardRef<MessageListHandle, MessageListProps>(({
|
|||||||
shouldAnimateUserMessage={shouldAnimateUserMessage}
|
shouldAnimateUserMessage={shouldAnimateUserMessage}
|
||||||
onUserAnimationConsumed={onUserAnimationConsumed}
|
onUserAnimationConsumed={onUserAnimationConsumed}
|
||||||
activeStreamingPhase={activeStreamingPhase}
|
activeStreamingPhase={activeStreamingPhase}
|
||||||
|
reviewTransferDirection={reviewTransferDirection}
|
||||||
/>
|
/>
|
||||||
{trailingStreamingEntry ? (
|
{trailingStreamingEntry ? (
|
||||||
<StreamingTailContent
|
<StreamingTailContent
|
||||||
@@ -1709,6 +1747,7 @@ const MessageList = React.forwardRef<MessageListHandle, MessageListProps>(({
|
|||||||
onUserAnimationConsumed={onUserAnimationConsumed}
|
onUserAnimationConsumed={onUserAnimationConsumed}
|
||||||
activeStreamingMessageId={activeStreamingMessageId}
|
activeStreamingMessageId={activeStreamingMessageId}
|
||||||
activeStreamingPhase={activeStreamingPhase}
|
activeStreamingPhase={activeStreamingPhase}
|
||||||
|
reviewTransferDirection={reviewTransferDirection}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -48,9 +48,8 @@ import { useI18n } from '@/lib/i18n';
|
|||||||
import { extractLoopbackUrls } from '@/lib/url';
|
import { extractLoopbackUrls } from '@/lib/url';
|
||||||
import { useDeviceInfo } from '@/lib/device';
|
import { useDeviceInfo } from '@/lib/device';
|
||||||
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
|
import { FileTypeIcon } from '@/components/icons/FileTypeIcon';
|
||||||
import { useGlobalSessionsStore } from '@/stores/useGlobalSessionsStore';
|
|
||||||
import {
|
import {
|
||||||
getReviewTransferDirection,
|
type ReviewTransferDirection,
|
||||||
sendImplementationResponseToReviewer,
|
sendImplementationResponseToReviewer,
|
||||||
sendReviewFeedbackToOriginal,
|
sendReviewFeedbackToOriginal,
|
||||||
} from '@/lib/reviewFlow';
|
} from '@/lib/reviewFlow';
|
||||||
@@ -366,6 +365,7 @@ interface MessageBodyProps {
|
|||||||
errorVariant?: 'error' | 'info';
|
errorVariant?: 'error' | 'info';
|
||||||
userActionsMode?: 'inline' | 'external-content' | 'external-actions';
|
userActionsMode?: 'inline' | 'external-content' | 'external-actions';
|
||||||
stickyUserHeaderEnabled?: boolean;
|
stickyUserHeaderEnabled?: boolean;
|
||||||
|
reviewTransferDirection?: ReviewTransferDirection | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TOOL_REVEAL_CACHE_MAX = 200;
|
const TOOL_REVEAL_CACHE_MAX = 200;
|
||||||
@@ -967,6 +967,7 @@ const AssistantMessageBody = React.memo(({
|
|||||||
turnGroupingContext,
|
turnGroupingContext,
|
||||||
errorMessage,
|
errorMessage,
|
||||||
errorVariant = 'error',
|
errorVariant = 'error',
|
||||||
|
reviewTransferDirection = null,
|
||||||
}: Omit<MessageBodyProps, 'isUser'>) => {
|
}: Omit<MessageBodyProps, 'isUser'>) => {
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const chatSurfaceMode = useChatSurfaceMode();
|
const chatSurfaceMode = useChatSurfaceMode();
|
||||||
@@ -1121,26 +1122,15 @@ const AssistantMessageBody = React.memo(({
|
|||||||
const createSessionFromAssistantMessage = useSessionUIStore((state) => state.createSessionFromAssistantMessage);
|
const createSessionFromAssistantMessage = useSessionUIStore((state) => state.createSessionFromAssistantMessage);
|
||||||
const currentSessionId = useSessionUIStore((state) => state.currentSessionId);
|
const currentSessionId = useSessionUIStore((state) => state.currentSessionId);
|
||||||
const getDirectoryForSession = useSessionUIStore((state) => state.getDirectoryForSession);
|
const getDirectoryForSession = useSessionUIStore((state) => state.getDirectoryForSession);
|
||||||
const currentSession = useGlobalSessionsStore((state) => {
|
|
||||||
if (!sessionId) return null;
|
|
||||||
for (const candidate of state.activeSessions) {
|
|
||||||
if (candidate.id === sessionId) return candidate;
|
|
||||||
}
|
|
||||||
for (const candidate of state.archivedSessions) {
|
|
||||||
if (candidate.id === sessionId) return candidate;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
const openMultiRunLauncherWithPrompt = useUIStore((state) => state.openMultiRunLauncherWithPrompt);
|
const openMultiRunLauncherWithPrompt = useUIStore((state) => state.openMultiRunLauncherWithPrompt);
|
||||||
const projects = useProjectsStore((state) => state.projects);
|
const projects = useProjectsStore((state) => state.projects);
|
||||||
const effectiveDirectory = useEffectiveDirectory();
|
const effectiveDirectory = useEffectiveDirectory();
|
||||||
const currentReviewTransferDirection = getReviewTransferDirection(currentSession);
|
const isReviewSessionView = reviewTransferDirection === 'review-to-original';
|
||||||
const isReviewSessionView = currentReviewTransferDirection === 'review-to-original';
|
const effectiveReviewTransferDirection = (!isMobile && !isVSCode) ? reviewTransferDirection : null;
|
||||||
const reviewTransferDirection = (!isMobile && !isVSCode) ? currentReviewTransferDirection : null;
|
|
||||||
const reviewTransferAction = React.useMemo(() => {
|
const reviewTransferAction = React.useMemo(() => {
|
||||||
const transferText = assistantPlanText.trim();
|
const transferText = assistantPlanText.trim();
|
||||||
if (!sessionId || !effectiveDirectory || !transferText || !reviewTransferDirection) return undefined;
|
if (!sessionId || !effectiveDirectory || !transferText || !effectiveReviewTransferDirection) return undefined;
|
||||||
if (reviewTransferDirection === 'review-to-original') {
|
if (effectiveReviewTransferDirection === 'review-to-original') {
|
||||||
return {
|
return {
|
||||||
ariaLabel: t('chat.messageBody.actions.sendReviewFeedback'),
|
ariaLabel: t('chat.messageBody.actions.sendReviewFeedback'),
|
||||||
tooltip: t('chat.messageBody.actions.sendReviewFeedback'),
|
tooltip: t('chat.messageBody.actions.sendReviewFeedback'),
|
||||||
@@ -1164,7 +1154,7 @@ const AssistantMessageBody = React.memo(({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}, [assistantPlanText, effectiveDirectory, reviewTransferDirection, sessionId, t]);
|
}, [assistantPlanText, effectiveDirectory, effectiveReviewTransferDirection, sessionId, t]);
|
||||||
const [isPlanDialogOpen, setIsPlanDialogOpen] = React.useState(false);
|
const [isPlanDialogOpen, setIsPlanDialogOpen] = React.useState(false);
|
||||||
const [isSavingPlan, setIsSavingPlan] = React.useState(false);
|
const [isSavingPlan, setIsSavingPlan] = React.useState(false);
|
||||||
const [isForkDialogOpen, setIsForkDialogOpen] = React.useState(false);
|
const [isForkDialogOpen, setIsForkDialogOpen] = React.useState(false);
|
||||||
|
|||||||
@@ -226,7 +226,9 @@ export const sendImplementationResponseToReviewer = async (originalSessionID: st
|
|||||||
openReviewSessionPanel(directory, reviewSession);
|
openReviewSessionPanel(directory, reviewSession);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getReviewTransferDirection = (session: Session | null | undefined): 'review-to-original' | 'original-to-review' | null => {
|
export type ReviewTransferDirection = 'review-to-original' | 'original-to-review';
|
||||||
|
|
||||||
|
export const getReviewTransferDirection = (session: Session | null | undefined): ReviewTransferDirection | null => {
|
||||||
if (isReviewSession(session)) return 'review-to-original';
|
if (isReviewSession(session)) return 'review-to-original';
|
||||||
if (getReviewSessionID(session)) return 'original-to-review';
|
if (getReviewSessionID(session)) return 'original-to-review';
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
Reference in New Issue
Block a user