({
messages,
setMessages,
}: UseChatMessagesOptions)
| 55 | * @returns Message tree, pagination state, and handlers |
| 56 | */ |
| 57 | export function useChatMessages({ |
| 58 | messages, |
| 59 | setMessages, |
| 60 | }: UseChatMessagesOptions): UseChatMessagesReturn { |
| 61 | // Message pagination state |
| 62 | const [visibleMessageCount, setVisibleMessageCount] = |
| 63 | useState(MESSAGE_BATCH_SIZE) |
| 64 | |
| 65 | // Reset visible message count when messages are cleared or conversation changes |
| 66 | useEffect(() => { |
| 67 | if (messages.length <= MESSAGE_BATCH_SIZE) { |
| 68 | setVisibleMessageCount(MESSAGE_BATCH_SIZE) |
| 69 | } |
| 70 | }, [messages.length]) |
| 71 | |
| 72 | // Ref to track user-initiated collapse (prevents auto-scroll during collapse) |
| 73 | const isUserCollapsingRef = useRef<boolean>(false) |
| 74 | |
| 75 | /** |
| 76 | * Returns true if user is currently collapsing. |
| 77 | * Used by scroll management to prevent auto-scroll during collapse. |
| 78 | */ |
| 79 | const isUserCollapsing = useCallback(() => { |
| 80 | return isUserCollapsingRef.current |
| 81 | }, []) |
| 82 | |
| 83 | /** |
| 84 | * Toggles the collapsed state of a block or agent message. |
| 85 | * Handles both top-level agent messages and nested content blocks. |
| 86 | */ |
| 87 | const handleCollapseToggle = useCallback( |
| 88 | (id: string) => { |
| 89 | // Set flag to prevent auto-scroll during user-initiated collapse |
| 90 | isUserCollapsingRef.current = true |
| 91 | |
| 92 | // Find and toggle the block's isCollapsed property |
| 93 | setMessages((prevMessages) => { |
| 94 | return prevMessages.map((message) => { |
| 95 | // Handle agent variant messages |
| 96 | if (message.variant === 'agent' && message.id === id) { |
| 97 | const wasCollapsed = message.metadata?.isCollapsed ?? false |
| 98 | return { |
| 99 | ...message, |
| 100 | metadata: { |
| 101 | ...message.metadata, |
| 102 | isCollapsed: !wasCollapsed, |
| 103 | userOpened: wasCollapsed, // Mark as user-opened if expanding |
| 104 | }, |
| 105 | } |
| 106 | } |
| 107 | |
| 108 | // Handle blocks within messages |
| 109 | if (!message.blocks) return message |
| 110 | |
| 111 | const updateBlocksRecursively = ( |
| 112 | blocks: ContentBlock[], |
| 113 | ): ContentBlock[] => { |
| 114 | let foundTarget = false |
no test coverage detected