perf: reduce chat streaming rerenders
- Keep historical chat messages stable during streaming - Isolate active stream state to the current message path - Reduce chat container churn from sync message updates
This commit is contained in:
@@ -106,6 +106,7 @@ type ChatViewportProps = {
|
|||||||
isLoadingOlder: boolean;
|
isLoadingOlder: boolean;
|
||||||
sessionIsWorking: boolean;
|
sessionIsWorking: boolean;
|
||||||
streamingMessageId: string | null;
|
streamingMessageId: string | null;
|
||||||
|
activeStreamingPhase: import('./message/types').StreamPhase | null;
|
||||||
retryOverlay: {
|
retryOverlay: {
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
message: string;
|
message: string;
|
||||||
@@ -135,6 +136,7 @@ const ChatViewport = React.memo(({
|
|||||||
isLoadingOlder,
|
isLoadingOlder,
|
||||||
sessionIsWorking,
|
sessionIsWorking,
|
||||||
streamingMessageId,
|
streamingMessageId,
|
||||||
|
activeStreamingPhase,
|
||||||
retryOverlay,
|
retryOverlay,
|
||||||
handleMessageContentChange,
|
handleMessageContentChange,
|
||||||
getAnimationHandlers,
|
getAnimationHandlers,
|
||||||
@@ -173,6 +175,7 @@ const ChatViewport = React.memo(({
|
|||||||
messages={renderedMessages}
|
messages={renderedMessages}
|
||||||
sessionIsWorking={sessionIsWorking}
|
sessionIsWorking={sessionIsWorking}
|
||||||
activeStreamingMessageId={streamingMessageId}
|
activeStreamingMessageId={streamingMessageId}
|
||||||
|
activeStreamingPhase={activeStreamingPhase}
|
||||||
retryOverlay={retryOverlay}
|
retryOverlay={retryOverlay}
|
||||||
onMessageContentChange={handleMessageContentChange}
|
onMessageContentChange={handleMessageContentChange}
|
||||||
getAnimationHandlers={getAnimationHandlers}
|
getAnimationHandlers={getAnimationHandlers}
|
||||||
@@ -218,6 +221,7 @@ const ChatViewport = React.memo(({
|
|||||||
&& prev.isLoadingOlder === next.isLoadingOlder
|
&& prev.isLoadingOlder === next.isLoadingOlder
|
||||||
&& prev.sessionIsWorking === next.sessionIsWorking
|
&& prev.sessionIsWorking === next.sessionIsWorking
|
||||||
&& prev.streamingMessageId === next.streamingMessageId
|
&& prev.streamingMessageId === next.streamingMessageId
|
||||||
|
&& prev.activeStreamingPhase === next.activeStreamingPhase
|
||||||
&& prev.retryOverlay === next.retryOverlay
|
&& prev.retryOverlay === next.retryOverlay
|
||||||
&& prev.handleMessageContentChange === next.handleMessageContentChange
|
&& prev.handleMessageContentChange === next.handleMessageContentChange
|
||||||
&& prev.getAnimationHandlers === next.getAnimationHandlers
|
&& prev.getAnimationHandlers === next.getAnimationHandlers
|
||||||
@@ -296,6 +300,15 @@ export const ChatContainer: React.FC = () => {
|
|||||||
[currentSessionId],
|
[currentSessionId],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
const activeStreamingPhase = useStreamingStore(
|
||||||
|
React.useCallback(
|
||||||
|
(s) => {
|
||||||
|
if (!streamingMessageId) return null;
|
||||||
|
return s.messageStreamStates.get(streamingMessageId)?.phase ?? null;
|
||||||
|
},
|
||||||
|
[streamingMessageId],
|
||||||
|
),
|
||||||
|
);
|
||||||
const sessionMessageCount = useSessionMessageCount(currentSessionId ?? '');
|
const sessionMessageCount = useSessionMessageCount(currentSessionId ?? '');
|
||||||
const [suspendDetachedTailUpdates, setSuspendDetachedTailUpdates] = React.useState(false);
|
const [suspendDetachedTailUpdates, setSuspendDetachedTailUpdates] = React.useState(false);
|
||||||
// Messages from sync system
|
// Messages from sync system
|
||||||
@@ -803,6 +816,7 @@ export const ChatContainer: React.FC = () => {
|
|||||||
isLoadingOlder={timelineController.isLoadingOlder}
|
isLoadingOlder={timelineController.isLoadingOlder}
|
||||||
sessionIsWorking={sessionIsWorking}
|
sessionIsWorking={sessionIsWorking}
|
||||||
streamingMessageId={streamingMessageId}
|
streamingMessageId={streamingMessageId}
|
||||||
|
activeStreamingPhase={activeStreamingPhase}
|
||||||
retryOverlay={retryOverlay}
|
retryOverlay={retryOverlay}
|
||||||
handleMessageContentChange={handleMessageContentChange}
|
handleMessageContentChange={handleMessageContentChange}
|
||||||
getAnimationHandlers={getAnimationHandlers}
|
getAnimationHandlers={getAnimationHandlers}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import { useConfigStore } from '@/stores/useConfigStore';
|
|||||||
import { useFeatureFlagsStore } from '@/stores/useFeatureFlagsStore';
|
import { useFeatureFlagsStore } from '@/stores/useFeatureFlagsStore';
|
||||||
import { useUIStore } from '@/stores/useUIStore';
|
import { useUIStore } from '@/stores/useUIStore';
|
||||||
import { useContextStore } from '@/stores/contextStore';
|
import { useContextStore } from '@/stores/contextStore';
|
||||||
import { useStreamingStore } from '@/sync/streaming';
|
|
||||||
import { useSessionUIStore } from '@/sync/session-ui-store';
|
import { useSessionUIStore } from '@/sync/session-ui-store';
|
||||||
import { useSelectionStore } from '@/sync/selection-store';
|
import { useSelectionStore } from '@/sync/selection-store';
|
||||||
import * as sessionActions from '@/sync/session-actions';
|
import * as sessionActions from '@/sync/session-actions';
|
||||||
@@ -131,6 +130,7 @@ interface ChatMessageProps {
|
|||||||
turnGroupingContext?: TurnGroupingContext;
|
turnGroupingContext?: TurnGroupingContext;
|
||||||
assistantHeaderMessageId?: string;
|
assistantHeaderMessageId?: string;
|
||||||
isInActiveTurn?: boolean;
|
isInActiveTurn?: boolean;
|
||||||
|
activeStreamingPhase?: StreamPhase | null;
|
||||||
animateUserOnMount?: boolean;
|
animateUserOnMount?: boolean;
|
||||||
onUserAnimationConsumed?: (messageId: string) => void;
|
onUserAnimationConsumed?: (messageId: string) => void;
|
||||||
}
|
}
|
||||||
@@ -144,6 +144,7 @@ const ChatMessage: React.FC<ChatMessageProps> = ({
|
|||||||
turnGroupingContext,
|
turnGroupingContext,
|
||||||
assistantHeaderMessageId,
|
assistantHeaderMessageId,
|
||||||
isInActiveTurn = false,
|
isInActiveTurn = false,
|
||||||
|
activeStreamingPhase = null,
|
||||||
animateUserOnMount = false,
|
animateUserOnMount = false,
|
||||||
onUserAnimationConsumed,
|
onUserAnimationConsumed,
|
||||||
}) => {
|
}) => {
|
||||||
@@ -152,13 +153,6 @@ const ChatMessage: React.FC<ChatMessageProps> = ({
|
|||||||
const messageContainerRef = React.useRef<HTMLDivElement | null>(null);
|
const messageContainerRef = React.useRef<HTMLDivElement | null>(null);
|
||||||
|
|
||||||
const currentSessionId = useSessionUIStore((s) => s.currentSessionId);
|
const currentSessionId = useSessionUIStore((s) => s.currentSessionId);
|
||||||
const streamState = useStreamingStore((s) => s.messageStreamStates.get(message.info.id));
|
|
||||||
const lifecyclePhase = isInActiveTurn ? (streamState?.phase ?? null) : null;
|
|
||||||
|
|
||||||
const msgSessionId = (message.info as { sessionID?: string }).sessionID ?? currentSessionId ?? null;
|
|
||||||
const streamingMsgForSession = useStreamingStore((s) => msgSessionId ? s.streamingMessageIds.get(msgSessionId) ?? null : null);
|
|
||||||
const isStreamingMessage = isInActiveTurn ? streamingMsgForSession === message.info.id : false;
|
|
||||||
const hasActiveStreamInSession = typeof streamingMsgForSession === 'string' && streamingMsgForSession.length > 0;
|
|
||||||
|
|
||||||
const getAgentModelForSession = useSelectionStore((s) => s.getAgentModelForSession);
|
const getAgentModelForSession = useSelectionStore((s) => s.getAgentModelForSession);
|
||||||
const getSessionModelSelection = useSelectionStore((s) => s.getSessionModelSelection);
|
const getSessionModelSelection = useSelectionStore((s) => s.getSessionModelSelection);
|
||||||
@@ -166,13 +160,8 @@ const ChatMessage: React.FC<ChatMessageProps> = ({
|
|||||||
const forkFromMessage = sessionActions.forkFromMessage;
|
const forkFromMessage = sessionActions.forkFromMessage;
|
||||||
|
|
||||||
streamPerfCount('ui.chat_message.render');
|
streamPerfCount('ui.chat_message.render');
|
||||||
if (isStreamingMessage) {
|
if (isInActiveTurn) {
|
||||||
streamPerfCount('ui.chat_message.render.streaming');
|
streamPerfCount('ui.chat_message.render.streaming');
|
||||||
} else if (hasActiveStreamInSession) {
|
|
||||||
streamPerfCount('ui.chat_message.render.static_during_stream');
|
|
||||||
if (!isInActiveTurn) {
|
|
||||||
streamPerfCount('ui.chat_message.render.static_outside_active_turn_during_stream');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const providers = useConfigStore.getState().providers;
|
const providers = useConfigStore.getState().providers;
|
||||||
@@ -596,11 +585,11 @@ const ChatMessage: React.FC<ChatMessageProps> = ({
|
|||||||
if (isMessageCompleted) {
|
if (isMessageCompleted) {
|
||||||
return 'completed';
|
return 'completed';
|
||||||
}
|
}
|
||||||
if (lifecyclePhase) {
|
if (isInActiveTurn) {
|
||||||
return lifecyclePhase;
|
return activeStreamingPhase ?? 'streaming';
|
||||||
}
|
}
|
||||||
return isStreamingMessage ? 'streaming' : 'completed';
|
return 'completed';
|
||||||
}, [isMessageCompleted, lifecyclePhase, isStreamingMessage]);
|
}, [activeStreamingPhase, isInActiveTurn, isMessageCompleted]);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (!isUser || !animateUserOnMount) {
|
if (!isUser || !animateUserOnMount) {
|
||||||
@@ -1148,6 +1137,7 @@ export default React.memo(ChatMessage, (prev, next) => {
|
|||||||
&& prev.turnGroupingContext === next.turnGroupingContext
|
&& prev.turnGroupingContext === next.turnGroupingContext
|
||||||
&& prev.assistantHeaderMessageId === next.assistantHeaderMessageId
|
&& prev.assistantHeaderMessageId === next.assistantHeaderMessageId
|
||||||
&& prev.isInActiveTurn === next.isInActiveTurn
|
&& prev.isInActiveTurn === next.isInActiveTurn
|
||||||
|
&& prev.activeStreamingPhase === next.activeStreamingPhase
|
||||||
&& prev.animateUserOnMount === next.animateUserOnMount
|
&& prev.animateUserOnMount === next.animateUserOnMount
|
||||||
&& prev.onUserAnimationConsumed === next.onUserAnimationConsumed;
|
&& prev.onUserAnimationConsumed === next.onUserAnimationConsumed;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { useUIStore } from '@/stores/useUIStore';
|
|||||||
import { FadeInDisabledProvider } from './message/FadeInOnReveal';
|
import { FadeInDisabledProvider } from './message/FadeInOnReveal';
|
||||||
import { hasPendingUserSendAnimation, consumePendingUserSendAnimation } from '@/lib/userSendAnimation';
|
import { hasPendingUserSendAnimation, consumePendingUserSendAnimation } from '@/lib/userSendAnimation';
|
||||||
import { streamPerfCount, streamPerfMeasure } from '@/stores/utils/streamDebug';
|
import { streamPerfCount, streamPerfMeasure } from '@/stores/utils/streamDebug';
|
||||||
|
import type { StreamPhase } from './message/types';
|
||||||
|
|
||||||
const MESSAGE_LIST_VIRTUALIZE_THRESHOLD = 40;
|
const MESSAGE_LIST_VIRTUALIZE_THRESHOLD = 40;
|
||||||
const MESSAGE_LIST_OVERSCAN = 6;
|
const MESSAGE_LIST_OVERSCAN = 6;
|
||||||
@@ -304,6 +305,7 @@ interface MessageListProps {
|
|||||||
messages: ChatMessageEntry[];
|
messages: ChatMessageEntry[];
|
||||||
sessionIsWorking?: boolean;
|
sessionIsWorking?: boolean;
|
||||||
activeStreamingMessageId?: string | null;
|
activeStreamingMessageId?: string | null;
|
||||||
|
activeStreamingPhase?: StreamPhase | null;
|
||||||
retryOverlay?: {
|
retryOverlay?: {
|
||||||
sessionId: string;
|
sessionId: string;
|
||||||
message: string;
|
message: string;
|
||||||
@@ -347,6 +349,7 @@ interface MessageRowProps {
|
|||||||
turnGroupingContext?: TurnGroupingContext;
|
turnGroupingContext?: TurnGroupingContext;
|
||||||
assistantHeaderMessageId?: string;
|
assistantHeaderMessageId?: string;
|
||||||
isInActiveTurn?: boolean;
|
isInActiveTurn?: boolean;
|
||||||
|
activeStreamingPhase?: StreamPhase | null;
|
||||||
animateUserOnMount?: boolean;
|
animateUserOnMount?: boolean;
|
||||||
onUserAnimationConsumed?: (messageId: string) => void;
|
onUserAnimationConsumed?: (messageId: string) => void;
|
||||||
onContentChange: (reason?: ContentChangeReason) => void;
|
onContentChange: (reason?: ContentChangeReason) => void;
|
||||||
@@ -361,6 +364,7 @@ const MessageRow = React.memo<MessageRowProps>(({
|
|||||||
turnGroupingContext,
|
turnGroupingContext,
|
||||||
assistantHeaderMessageId,
|
assistantHeaderMessageId,
|
||||||
isInActiveTurn,
|
isInActiveTurn,
|
||||||
|
activeStreamingPhase,
|
||||||
animateUserOnMount,
|
animateUserOnMount,
|
||||||
onUserAnimationConsumed,
|
onUserAnimationConsumed,
|
||||||
onContentChange,
|
onContentChange,
|
||||||
@@ -380,6 +384,7 @@ const MessageRow = React.memo<MessageRowProps>(({
|
|||||||
turnGroupingContext={turnGroupingContext}
|
turnGroupingContext={turnGroupingContext}
|
||||||
assistantHeaderMessageId={assistantHeaderMessageId}
|
assistantHeaderMessageId={assistantHeaderMessageId}
|
||||||
isInActiveTurn={isInActiveTurn}
|
isInActiveTurn={isInActiveTurn}
|
||||||
|
activeStreamingPhase={activeStreamingPhase}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}, (prev, next) => {
|
}, (prev, next) => {
|
||||||
@@ -404,6 +409,7 @@ const MessageRow = React.memo<MessageRowProps>(({
|
|||||||
&& prevTurn?.activityParts === nextTurn?.activityParts
|
&& prevTurn?.activityParts === nextTurn?.activityParts
|
||||||
&& prev.assistantHeaderMessageId === next.assistantHeaderMessageId
|
&& prev.assistantHeaderMessageId === next.assistantHeaderMessageId
|
||||||
&& prev.isInActiveTurn === next.isInActiveTurn
|
&& prev.isInActiveTurn === next.isInActiveTurn
|
||||||
|
&& prev.activeStreamingPhase === next.activeStreamingPhase
|
||||||
&& 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
|
||||||
@@ -430,6 +436,7 @@ interface TurnBlockProps {
|
|||||||
shouldAnimateUserMessage: (message: ChatMessageEntry) => boolean;
|
shouldAnimateUserMessage: (message: ChatMessageEntry) => boolean;
|
||||||
onUserAnimationConsumed: (messageId: string) => void;
|
onUserAnimationConsumed: (messageId: string) => void;
|
||||||
activeStreamingMessageId?: string | null;
|
activeStreamingMessageId?: string | null;
|
||||||
|
activeStreamingPhase?: StreamPhase | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TurnBlock: React.FC<TurnBlockProps> = ({
|
const TurnBlock: React.FC<TurnBlockProps> = ({
|
||||||
@@ -447,6 +454,7 @@ const TurnBlock: React.FC<TurnBlockProps> = ({
|
|||||||
shouldAnimateUserMessage,
|
shouldAnimateUserMessage,
|
||||||
onUserAnimationConsumed,
|
onUserAnimationConsumed,
|
||||||
activeStreamingMessageId,
|
activeStreamingMessageId,
|
||||||
|
activeStreamingPhase,
|
||||||
}) => {
|
}) => {
|
||||||
const turnUiState = turnUiStates.get(turn.turnId) ?? { isExpanded: defaultActivityExpanded };
|
const turnUiState = turnUiStates.get(turn.turnId) ?? { isExpanded: defaultActivityExpanded };
|
||||||
const handleToggleTurnGroup = React.useCallback(() => {
|
const handleToggleTurnGroup = React.useCallback(() => {
|
||||||
@@ -656,6 +664,7 @@ const TurnBlock: React.FC<TurnBlockProps> = ({
|
|||||||
turnGroupingContext={turnGroupingContext}
|
turnGroupingContext={turnGroupingContext}
|
||||||
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}
|
||||||
animateUserOnMount={shouldAnimateUserMessage(message)}
|
animateUserOnMount={shouldAnimateUserMessage(message)}
|
||||||
onUserAnimationConsumed={onUserAnimationConsumed}
|
onUserAnimationConsumed={onUserAnimationConsumed}
|
||||||
onContentChange={onMessageContentChange}
|
onContentChange={onMessageContentChange}
|
||||||
@@ -681,6 +690,7 @@ const TurnBlock: React.FC<TurnBlockProps> = ({
|
|||||||
turnUiState.isExpanded,
|
turnUiState.isExpanded,
|
||||||
turnGroupingContextBase,
|
turnGroupingContextBase,
|
||||||
streamingAssistantMessageId,
|
streamingAssistantMessageId,
|
||||||
|
activeStreamingPhase,
|
||||||
visibleAssistantMessages,
|
visibleAssistantMessages,
|
||||||
visibleAssistantIds,
|
visibleAssistantIds,
|
||||||
activityOwnerMessageId,
|
activityOwnerMessageId,
|
||||||
@@ -717,6 +727,7 @@ interface UngroupedMessageRowProps {
|
|||||||
shouldAnimateUserMessage: (message: ChatMessageEntry) => boolean;
|
shouldAnimateUserMessage: (message: ChatMessageEntry) => boolean;
|
||||||
onUserAnimationConsumed: (messageId: string) => void;
|
onUserAnimationConsumed: (messageId: string) => void;
|
||||||
activeStreamingMessageId?: string | null;
|
activeStreamingMessageId?: string | null;
|
||||||
|
activeStreamingPhase?: StreamPhase | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const UngroupedMessageRow: React.FC<UngroupedMessageRowProps> = React.memo(({
|
const UngroupedMessageRow: React.FC<UngroupedMessageRowProps> = React.memo(({
|
||||||
@@ -729,6 +740,7 @@ const UngroupedMessageRow: React.FC<UngroupedMessageRowProps> = React.memo(({
|
|||||||
shouldAnimateUserMessage,
|
shouldAnimateUserMessage,
|
||||||
onUserAnimationConsumed,
|
onUserAnimationConsumed,
|
||||||
activeStreamingMessageId,
|
activeStreamingMessageId,
|
||||||
|
activeStreamingPhase,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<MessageRow
|
<MessageRow
|
||||||
@@ -741,6 +753,7 @@ const UngroupedMessageRow: React.FC<UngroupedMessageRowProps> = React.memo(({
|
|||||||
animationHandlers={getAnimationHandlers(message.info.id)}
|
animationHandlers={getAnimationHandlers(message.info.id)}
|
||||||
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}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}, (prev, next) => {
|
}, (prev, next) => {
|
||||||
@@ -752,7 +765,8 @@ const UngroupedMessageRow: React.FC<UngroupedMessageRowProps> = React.memo(({
|
|||||||
&& prev.scrollToBottom === next.scrollToBottom
|
&& prev.scrollToBottom === next.scrollToBottom
|
||||||
&& prev.shouldAnimateUserMessage === next.shouldAnimateUserMessage
|
&& prev.shouldAnimateUserMessage === next.shouldAnimateUserMessage
|
||||||
&& prev.onUserAnimationConsumed === next.onUserAnimationConsumed
|
&& prev.onUserAnimationConsumed === next.onUserAnimationConsumed
|
||||||
&& prev.activeStreamingMessageId === next.activeStreamingMessageId;
|
&& prev.activeStreamingMessageId === next.activeStreamingMessageId
|
||||||
|
&& prev.activeStreamingPhase === next.activeStreamingPhase;
|
||||||
});
|
});
|
||||||
|
|
||||||
UngroupedMessageRow.displayName = 'UngroupedMessageRow';
|
UngroupedMessageRow.displayName = 'UngroupedMessageRow';
|
||||||
@@ -771,6 +785,7 @@ interface MessageListEntryProps {
|
|||||||
shouldAnimateUserMessage: (message: ChatMessageEntry) => boolean;
|
shouldAnimateUserMessage: (message: ChatMessageEntry) => boolean;
|
||||||
onUserAnimationConsumed: (messageId: string) => void;
|
onUserAnimationConsumed: (messageId: string) => void;
|
||||||
activeStreamingMessageId?: string | null;
|
activeStreamingMessageId?: string | null;
|
||||||
|
activeStreamingPhase?: StreamPhase | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const turnContainsMessageId = (turn: TurnRecord, messageId: string | null | undefined): boolean => {
|
const turnContainsMessageId = (turn: TurnRecord, messageId: string | null | undefined): boolean => {
|
||||||
@@ -799,6 +814,7 @@ const MessageListEntry: React.FC<MessageListEntryProps> = React.memo(({
|
|||||||
shouldAnimateUserMessage,
|
shouldAnimateUserMessage,
|
||||||
onUserAnimationConsumed,
|
onUserAnimationConsumed,
|
||||||
activeStreamingMessageId,
|
activeStreamingMessageId,
|
||||||
|
activeStreamingPhase,
|
||||||
}) => {
|
}) => {
|
||||||
if (entry.kind === 'ungrouped') {
|
if (entry.kind === 'ungrouped') {
|
||||||
return (
|
return (
|
||||||
@@ -812,6 +828,7 @@ const MessageListEntry: React.FC<MessageListEntryProps> = React.memo(({
|
|||||||
shouldAnimateUserMessage={shouldAnimateUserMessage}
|
shouldAnimateUserMessage={shouldAnimateUserMessage}
|
||||||
onUserAnimationConsumed={onUserAnimationConsumed}
|
onUserAnimationConsumed={onUserAnimationConsumed}
|
||||||
activeStreamingMessageId={activeStreamingMessageId}
|
activeStreamingMessageId={activeStreamingMessageId}
|
||||||
|
activeStreamingPhase={activeStreamingPhase}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -828,6 +845,7 @@ const MessageListEntry: React.FC<MessageListEntryProps> = React.memo(({
|
|||||||
shouldAnimateUserMessage={shouldAnimateUserMessage}
|
shouldAnimateUserMessage={shouldAnimateUserMessage}
|
||||||
onUserAnimationConsumed={onUserAnimationConsumed}
|
onUserAnimationConsumed={onUserAnimationConsumed}
|
||||||
activeStreamingMessageId={activeStreamingMessageId}
|
activeStreamingMessageId={activeStreamingMessageId}
|
||||||
|
activeStreamingPhase={activeStreamingPhase}
|
||||||
onMessageContentChange={onMessageContentChange}
|
onMessageContentChange={onMessageContentChange}
|
||||||
getAnimationHandlers={getAnimationHandlers}
|
getAnimationHandlers={getAnimationHandlers}
|
||||||
scrollToBottom={scrollToBottom}
|
scrollToBottom={scrollToBottom}
|
||||||
@@ -871,6 +889,22 @@ function areMessageListEntryPropsEqual(prevProps: MessageListEntryProps, nextPro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prevProps.activeStreamingPhase !== nextProps.activeStreamingPhase) {
|
||||||
|
const prevAffected = turnContainsMessageId(prevEntry.turn, prevProps.activeStreamingMessageId);
|
||||||
|
const nextAffected = turnContainsMessageId(nextEntry.turn, nextProps.activeStreamingMessageId);
|
||||||
|
if (prevAffected || nextAffected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevProps.activeStreamingPhase !== nextProps.activeStreamingPhase) {
|
||||||
|
const prevAffected = turnContainsMessageId(prevEntry.turn, prevProps.activeStreamingMessageId);
|
||||||
|
const nextAffected = turnContainsMessageId(nextEntry.turn, nextProps.activeStreamingMessageId);
|
||||||
|
if (prevAffected || nextAffected) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -884,6 +918,20 @@ function areMessageListEntryPropsEqual(prevProps: MessageListEntryProps, nextPro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (prevProps.activeStreamingPhase !== nextProps.activeStreamingPhase) {
|
||||||
|
const messageId = prevEntry.message.info.id;
|
||||||
|
if (prevProps.activeStreamingMessageId === messageId || nextProps.activeStreamingMessageId === messageId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevProps.activeStreamingPhase !== nextProps.activeStreamingPhase) {
|
||||||
|
const messageId = prevEntry.message.info.id;
|
||||||
|
if (prevProps.activeStreamingMessageId === messageId || nextProps.activeStreamingMessageId === messageId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
areRenderRelevantMessagesEqual(prevEntry.message, nextEntry.message)
|
areRenderRelevantMessagesEqual(prevEntry.message, nextEntry.message)
|
||||||
&& areOptionalRenderRelevantMessagesEqual(prevEntry.previousMessage, nextEntry.previousMessage)
|
&& areOptionalRenderRelevantMessagesEqual(prevEntry.previousMessage, nextEntry.previousMessage)
|
||||||
@@ -912,7 +960,8 @@ const StaticHistoryList: React.FC<{
|
|||||||
chatRenderMode: 'sorted' | 'live';
|
chatRenderMode: 'sorted' | 'live';
|
||||||
shouldAnimateUserMessage: (message: ChatMessageEntry) => boolean;
|
shouldAnimateUserMessage: (message: ChatMessageEntry) => boolean;
|
||||||
onUserAnimationConsumed: (messageId: string) => void;
|
onUserAnimationConsumed: (messageId: string) => void;
|
||||||
}> = React.memo(({ entries, shouldVirtualize, virtualRows, totalSize, measureElement, contentRef, onMessageContentChange, getAnimationHandlers, scrollToBottom, stickyUserHeader, defaultActivityExpanded, turnUiStates, onToggleTurnGroup, chatRenderMode, shouldAnimateUserMessage, onUserAnimationConsumed }) => {
|
activeStreamingPhase?: StreamPhase | null;
|
||||||
|
}> = React.memo(({ entries, shouldVirtualize, virtualRows, totalSize, measureElement, contentRef, onMessageContentChange, getAnimationHandlers, scrollToBottom, stickyUserHeader, defaultActivityExpanded, turnUiStates, onToggleTurnGroup, chatRenderMode, shouldAnimateUserMessage, onUserAnimationConsumed, activeStreamingPhase }) => {
|
||||||
const renderEntry = React.useCallback((entry: RenderEntry) => {
|
const renderEntry = React.useCallback((entry: RenderEntry) => {
|
||||||
return (
|
return (
|
||||||
<MessageListEntry
|
<MessageListEntry
|
||||||
@@ -930,9 +979,10 @@ const StaticHistoryList: React.FC<{
|
|||||||
shouldAnimateUserMessage={shouldAnimateUserMessage}
|
shouldAnimateUserMessage={shouldAnimateUserMessage}
|
||||||
onUserAnimationConsumed={onUserAnimationConsumed}
|
onUserAnimationConsumed={onUserAnimationConsumed}
|
||||||
activeStreamingMessageId={null}
|
activeStreamingMessageId={null}
|
||||||
|
activeStreamingPhase={activeStreamingPhase}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}, [chatRenderMode, defaultActivityExpanded, getAnimationHandlers, onMessageContentChange, onToggleTurnGroup, onUserAnimationConsumed, scrollToBottom, shouldAnimateUserMessage, stickyUserHeader, turnUiStates]);
|
}, [activeStreamingPhase, chatRenderMode, defaultActivityExpanded, getAnimationHandlers, onMessageContentChange, onToggleTurnGroup, onUserAnimationConsumed, scrollToBottom, shouldAnimateUserMessage, stickyUserHeader, turnUiStates]);
|
||||||
|
|
||||||
const paddingTop = shouldVirtualize && virtualRows.length > 0
|
const paddingTop = shouldVirtualize && virtualRows.length > 0
|
||||||
? virtualRows[0]?.start ?? 0
|
? virtualRows[0]?.start ?? 0
|
||||||
@@ -995,7 +1045,8 @@ const StaticHistoryList: React.FC<{
|
|||||||
&& prevProps.onToggleTurnGroup === nextProps.onToggleTurnGroup
|
&& prevProps.onToggleTurnGroup === nextProps.onToggleTurnGroup
|
||||||
&& prevProps.chatRenderMode === nextProps.chatRenderMode
|
&& prevProps.chatRenderMode === nextProps.chatRenderMode
|
||||||
&& prevProps.shouldAnimateUserMessage === nextProps.shouldAnimateUserMessage
|
&& prevProps.shouldAnimateUserMessage === nextProps.shouldAnimateUserMessage
|
||||||
&& prevProps.onUserAnimationConsumed === nextProps.onUserAnimationConsumed;
|
&& prevProps.onUserAnimationConsumed === nextProps.onUserAnimationConsumed
|
||||||
|
&& prevProps.activeStreamingPhase === nextProps.activeStreamingPhase;
|
||||||
});
|
});
|
||||||
|
|
||||||
StaticHistoryList.displayName = 'StaticHistoryList';
|
StaticHistoryList.displayName = 'StaticHistoryList';
|
||||||
@@ -1014,6 +1065,7 @@ const StreamingTailContent: React.FC<{
|
|||||||
shouldAnimateUserMessage: (message: ChatMessageEntry) => boolean;
|
shouldAnimateUserMessage: (message: ChatMessageEntry) => boolean;
|
||||||
onUserAnimationConsumed: (messageId: string) => void;
|
onUserAnimationConsumed: (messageId: string) => void;
|
||||||
activeStreamingMessageId?: string | null;
|
activeStreamingMessageId?: string | null;
|
||||||
|
activeStreamingPhase?: StreamPhase | null;
|
||||||
}> = React.memo(({
|
}> = React.memo(({
|
||||||
entry,
|
entry,
|
||||||
onMessageContentChange,
|
onMessageContentChange,
|
||||||
@@ -1028,6 +1080,7 @@ const StreamingTailContent: React.FC<{
|
|||||||
shouldAnimateUserMessage,
|
shouldAnimateUserMessage,
|
||||||
onUserAnimationConsumed,
|
onUserAnimationConsumed,
|
||||||
activeStreamingMessageId,
|
activeStreamingMessageId,
|
||||||
|
activeStreamingPhase,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<MessageListEntry
|
<MessageListEntry
|
||||||
@@ -1044,6 +1097,7 @@ const StreamingTailContent: React.FC<{
|
|||||||
shouldAnimateUserMessage={shouldAnimateUserMessage}
|
shouldAnimateUserMessage={shouldAnimateUserMessage}
|
||||||
onUserAnimationConsumed={onUserAnimationConsumed}
|
onUserAnimationConsumed={onUserAnimationConsumed}
|
||||||
activeStreamingMessageId={activeStreamingMessageId}
|
activeStreamingMessageId={activeStreamingMessageId}
|
||||||
|
activeStreamingPhase={activeStreamingPhase}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}, (prev, next) => {
|
}, (prev, next) => {
|
||||||
@@ -1059,7 +1113,8 @@ const StreamingTailContent: React.FC<{
|
|||||||
&& prev.chatRenderMode === next.chatRenderMode
|
&& prev.chatRenderMode === next.chatRenderMode
|
||||||
&& prev.shouldAnimateUserMessage === next.shouldAnimateUserMessage
|
&& prev.shouldAnimateUserMessage === next.shouldAnimateUserMessage
|
||||||
&& prev.onUserAnimationConsumed === next.onUserAnimationConsumed
|
&& prev.onUserAnimationConsumed === next.onUserAnimationConsumed
|
||||||
&& prev.activeStreamingMessageId === next.activeStreamingMessageId;
|
&& prev.activeStreamingMessageId === next.activeStreamingMessageId
|
||||||
|
&& prev.activeStreamingPhase === next.activeStreamingPhase;
|
||||||
});
|
});
|
||||||
|
|
||||||
StreamingTailContent.displayName = 'StreamingTailContent';
|
StreamingTailContent.displayName = 'StreamingTailContent';
|
||||||
@@ -1071,6 +1126,7 @@ const MessageList = React.forwardRef<MessageListHandle, MessageListProps>(({
|
|||||||
messages,
|
messages,
|
||||||
sessionIsWorking = false,
|
sessionIsWorking = false,
|
||||||
activeStreamingMessageId = null,
|
activeStreamingMessageId = null,
|
||||||
|
activeStreamingPhase = null,
|
||||||
retryOverlay = null,
|
retryOverlay = null,
|
||||||
onMessageContentChange,
|
onMessageContentChange,
|
||||||
getAnimationHandlers,
|
getAnimationHandlers,
|
||||||
@@ -1760,6 +1816,7 @@ const MessageList = React.forwardRef<MessageListHandle, MessageListProps>(({
|
|||||||
chatRenderMode={chatRenderMode}
|
chatRenderMode={chatRenderMode}
|
||||||
shouldAnimateUserMessage={shouldAnimateUserMessage}
|
shouldAnimateUserMessage={shouldAnimateUserMessage}
|
||||||
onUserAnimationConsumed={onUserAnimationConsumed}
|
onUserAnimationConsumed={onUserAnimationConsumed}
|
||||||
|
activeStreamingPhase={activeStreamingPhase}
|
||||||
/>
|
/>
|
||||||
{trailingStreamingEntry ? (
|
{trailingStreamingEntry ? (
|
||||||
<StreamingTailContent
|
<StreamingTailContent
|
||||||
@@ -1776,6 +1833,7 @@ const MessageList = React.forwardRef<MessageListHandle, MessageListProps>(({
|
|||||||
shouldAnimateUserMessage={shouldAnimateUserMessage}
|
shouldAnimateUserMessage={shouldAnimateUserMessage}
|
||||||
onUserAnimationConsumed={onUserAnimationConsumed}
|
onUserAnimationConsumed={onUserAnimationConsumed}
|
||||||
activeStreamingMessageId={activeStreamingMessageId}
|
activeStreamingMessageId={activeStreamingMessageId}
|
||||||
|
activeStreamingPhase={activeStreamingPhase}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -36,7 +36,14 @@ type SyncSystem = {
|
|||||||
directory: string
|
directory: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const SyncContext = createContext<SyncSystem | null>(null)
|
const SYNC_CONTEXT_GLOBAL_KEY = "__openchamber_sync_context__"
|
||||||
|
type SyncGlobal = typeof globalThis & {
|
||||||
|
[SYNC_CONTEXT_GLOBAL_KEY]?: React.Context<SyncSystem | null>
|
||||||
|
}
|
||||||
|
|
||||||
|
const syncGlobal = globalThis as SyncGlobal
|
||||||
|
const SyncContext = syncGlobal[SYNC_CONTEXT_GLOBAL_KEY] ?? createContext<SyncSystem | null>(null)
|
||||||
|
syncGlobal[SYNC_CONTEXT_GLOBAL_KEY] = SyncContext
|
||||||
|
|
||||||
function useSyncSystem() {
|
function useSyncSystem() {
|
||||||
const ctx = useContext(SyncContext)
|
const ctx = useContext(SyncContext)
|
||||||
@@ -1072,9 +1079,3 @@ const EMPTY_MESSAGES: Message[] = []
|
|||||||
const EMPTY_PARTS: Part[] = []
|
const EMPTY_PARTS: Part[] = []
|
||||||
const EMPTY_PERMISSION_REQUESTS: PermissionRequest[] = []
|
const EMPTY_PERMISSION_REQUESTS: PermissionRequest[] = []
|
||||||
const EMPTY_QUESTION_REQUESTS: QuestionRequest[] = []
|
const EMPTY_QUESTION_REQUESTS: QuestionRequest[] = []
|
||||||
|
|
||||||
if (import.meta.hot) {
|
|
||||||
import.meta.hot.accept(() => {
|
|
||||||
import.meta.hot?.invalidate()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|||||||
Reference in New Issue
Block a user