fc9723949b
This PR: - Breaks useAgentChatData into focused effect components (streaming, fetch, init, auto-scroll, diff sync) - Splits message list into non-last (stable) + last (streaming/error) to prevent full re-renders on each stream chunk - Adds scroll-to-bottom button and MutationObserver-based auto-scroll on thread switch - Lifts loading state from context to atoms - Adds areEqual to selector factories We could improve further but this sets up a robust architecture for further refactoring. ## Messages flow The flow of messages loading and streaming is now more solid. Everything goes out from `AgentChatAiSdkStreamEffect`, whether loaded from the DB or streaming directly, and every consumers is using only one atom `agentChatMessagesComponentFamilyState` ## Data sync effect with callbacks new hook See `packages/twenty-front/src/modules/apollo/hooks/useQueryWithCallbacks.ts` which allows to fix Apollo v4 migration leftovers and is an implementation of the pattern we talked about with @charlesBochet We could refine this pattern in another PR. # Before https://github.com/user-attachments/assets/84e7a96f-6790-405d-8a73-2dacbf783be5 # After https://github.com/user-attachments/assets/4c692e3a-2413-4513-abcc-44d0da311203 Co-authored-by: Charles Bochet <charles@twenty.com>
50 lines
1.7 KiB
TypeScript
50 lines
1.7 KiB
TypeScript
import { agentChatIsScrolledToBottomSelector } from '@/ai/states/agentChatIsScrolledToBottomSelector';
|
|
import { scrollAIChatToBottom } from '@/ai/utils/scrollAIChatToBottom';
|
|
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
|
import { styled } from '@linaria/react';
|
|
import { IconArrowDown } from 'twenty-ui/display';
|
|
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
|
|
|
const StyledScrollToBottomButton = styled.button<{ isVisible: boolean }>`
|
|
align-items: center;
|
|
background: ${themeCssVariables.background.primary};
|
|
border: 1px solid ${themeCssVariables.border.color.medium};
|
|
border-radius: ${themeCssVariables.border.radius.rounded};
|
|
bottom: ${themeCssVariables.spacing[3]};
|
|
box-shadow: ${themeCssVariables.boxShadow.light};
|
|
color: ${themeCssVariables.font.color.secondary};
|
|
cursor: pointer;
|
|
display: flex;
|
|
height: 32px;
|
|
justify-content: center;
|
|
left: 50%;
|
|
opacity: ${({ isVisible }) => (isVisible ? 1 : 0)};
|
|
pointer-events: ${({ isVisible }) => (isVisible ? 'auto' : 'none')};
|
|
position: absolute;
|
|
transform: translateX(-50%);
|
|
transition:
|
|
opacity calc(${themeCssVariables.animation.duration.normal} * 1s) ease,
|
|
background calc(${themeCssVariables.animation.duration.fast} * 1s) ease;
|
|
width: 32px;
|
|
z-index: 1;
|
|
|
|
&:hover {
|
|
background: ${themeCssVariables.background.tertiary};
|
|
}
|
|
`;
|
|
|
|
export const AIChatScrollToBottomButton = () => {
|
|
const agentChatIsScrolledToBottom = useAtomStateValue(
|
|
agentChatIsScrolledToBottomSelector,
|
|
);
|
|
|
|
return (
|
|
<StyledScrollToBottomButton
|
|
isVisible={!agentChatIsScrolledToBottom}
|
|
onClick={scrollAIChatToBottom}
|
|
>
|
|
<IconArrowDown size={16} />
|
|
</StyledScrollToBottomButton>
|
|
);
|
|
};
|