| .. | .. |
|---|
| 112 | 112 | saveTimer = setTimeout(() => persistMessages(map), 1000); |
|---|
| 113 | 113 | } |
|---|
| 114 | 114 | |
|---|
| 115 | +const PAGE_SIZE = 50; |
|---|
| 116 | + |
|---|
| 115 | 117 | // --- Context --- |
|---|
| 116 | 118 | |
|---|
| 117 | 119 | interface ChatContextValue { |
|---|
| .. | .. |
|---|
| 131 | 133 | createSession: (opts?: { project?: string; path?: string }) => void; |
|---|
| 132 | 134 | fetchProjects: () => void; |
|---|
| 133 | 135 | projects: PaiProject[]; |
|---|
| 136 | + loadMoreMessages: () => void; |
|---|
| 137 | + hasMoreMessages: boolean; |
|---|
| 134 | 138 | unreadCounts: Record<string, number>; |
|---|
| 135 | 139 | latestScreenshot: string | null; |
|---|
| 136 | 140 | requestScreenshot: () => void; |
|---|
| .. | .. |
|---|
| 155 | 159 | const [isTyping, setIsTyping] = useState(false); |
|---|
| 156 | 160 | // PAI projects list |
|---|
| 157 | 161 | const [projects, setProjects] = useState<PaiProject[]>([]); |
|---|
| 162 | + // Pagination: does the active session have more messages in storage? |
|---|
| 163 | + const [hasMoreMessages, setHasMoreMessages] = useState(false); |
|---|
| 158 | 164 | |
|---|
| 159 | 165 | const { |
|---|
| 160 | 166 | status, |
|---|
| .. | .. |
|---|
| 184 | 190 | if (prev) { |
|---|
| 185 | 191 | messagesMapRef.current[prev] = messages; |
|---|
| 186 | 192 | } |
|---|
| 187 | | - const stored = messagesMapRef.current[active.id] ?? []; |
|---|
| 188 | | - setMessages(stored); |
|---|
| 193 | + const all = messagesMapRef.current[active.id] ?? []; |
|---|
| 194 | + const page = all.length > PAGE_SIZE ? all.slice(-PAGE_SIZE) : all; |
|---|
| 195 | + setMessages(page); |
|---|
| 196 | + setHasMoreMessages(all.length > PAGE_SIZE); |
|---|
| 189 | 197 | setUnreadCounts((u) => { |
|---|
| 190 | 198 | if (!u[active.id]) return u; |
|---|
| 191 | 199 | const next = { ...u }; |
|---|
| .. | .. |
|---|
| 550 | 558 | sendCommand("projects"); |
|---|
| 551 | 559 | }, [sendCommand]); |
|---|
| 552 | 560 | |
|---|
| 561 | + const loadMoreMessages = useCallback(() => { |
|---|
| 562 | + setActiveSessionId((sessId) => { |
|---|
| 563 | + if (!sessId) return sessId; |
|---|
| 564 | + const all = messagesMapRef.current[sessId] ?? []; |
|---|
| 565 | + setMessages((current) => { |
|---|
| 566 | + if (current.length >= all.length) { |
|---|
| 567 | + setHasMoreMessages(false); |
|---|
| 568 | + return current; |
|---|
| 569 | + } |
|---|
| 570 | + const nextSize = Math.min(current.length + PAGE_SIZE, all.length); |
|---|
| 571 | + const page = all.slice(-nextSize); |
|---|
| 572 | + setHasMoreMessages(nextSize < all.length); |
|---|
| 573 | + return page; |
|---|
| 574 | + }); |
|---|
| 575 | + return sessId; |
|---|
| 576 | + }); |
|---|
| 577 | + }, []); |
|---|
| 578 | + |
|---|
| 553 | 579 | // --- Screenshot / navigation --- |
|---|
| 554 | 580 | const requestScreenshot = useCallback(() => { |
|---|
| 555 | 581 | sendCommand("screenshot"); |
|---|
| .. | .. |
|---|
| 581 | 607 | createSession, |
|---|
| 582 | 608 | fetchProjects, |
|---|
| 583 | 609 | projects, |
|---|
| 610 | + loadMoreMessages, |
|---|
| 611 | + hasMoreMessages, |
|---|
| 584 | 612 | unreadCounts, |
|---|
| 585 | 613 | latestScreenshot, |
|---|
| 586 | 614 | requestScreenshot, |
|---|