ckl - #7358
Closed
vesper-collab wants to merge 7 commits into
Closed
Conversation
vesper-collab
requested a deployment
to
ai-review-approved
August 27, 2026 12:45 — with
GitHub Actions
Waiting
|
Hi @vesper-collab, thank you for your first Pull Request! 🎉 🙌 Join Developer CommunityThanks so much for your contribution! We'd love to invite you to join the official QwenPaw developer group! You can find the Discord and DingTalk group links under the "Developer Community" section on our docs page: We truly appreciate your enthusiasm—and look forward to your future contributions! 😊 We'll review your PR soon. |
Port GET /api/chats/{chat_id} limit/before pagination from agentscope-ai#7049 and wire
the console to open chats with the latest 50 messages, plus a load-earlier
control that pages on metadata.original_id.
This is the frontend support @zhijianma asked for before merging agentscope-ai#7049.
cursor
Bot
force-pushed
the
cursor/chat-history-pagination-b439
branch
from
August 27, 2026 12:46
cdc7ecf to
72088e4
Compare
useSyncExternalStore re-renders forever if getHistoryPage returns a new object on every read. Point the ChatPage mocks at one reused empty page and document the same requirement on the real getter.
vesper-collab
marked this pull request as ready for review
August 27, 2026 13:04
Persist N in localStorage (default 50, clamp 1-10000), expose it on the Console settings Chat section and next to Load earlier, and use it for open, load-earlier, and live refetch of the latest window.
…transcript Slice the AgentScope Msg list (cursor = Msg.id / metadata.original_id) before agentscope_msg_to_message so opening a long chat converts only the requested window. Replace the vendor message list with a reverse virtual list so the Chat page mounts viewport plus overscan, keeps load-earlier scroll, and still sticks to new/streaming messages.
Stick to newest at scrollTop 0, restore the visible row after prepend or live-row growth, and put flex gap on the mounted rows so spacers do not steal spacing. isLast stays on the live edge. Tests cover viewport mount, prepend, stick-to-newest, onStartReached, and session listener cleanup.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Description
#7049 added optional
limit/beforepagination onGET /api/chats/{chat_id}so the console does not have to download an entire conversation at once. @zhijianma closed it with Close-and-review-later:Backend-only does not fix the freeze / timeout on long chats (#3915, #6635). This PR is the chat-history half of that work: window the stored history, convert only that window, and keep the Chat transcript from mounting every loaded bubble.
Backend (same public contract as #7049, still backward compatible):
limit— max most-recent source messages (AgentScopeMsgobjects), default0= full history,ge=0 le=10000before— cursor is the persistent AgentScopeMsg.id, exposed after conversion asmetadata.original_id.Message.idis a fresh uuid4 every request and is not used as a cursortotal(unwindowed source-message count) andhas_morelimitwindowMsglist beforeagentscope_msg_to_message. Opening a 540k-token / thousands-of-msgs chat no longer converts the full history into runtimeMessageobjects just to throw most of them away. OneMsgcan still expand into severalMessageobjects that shareoriginal_id; cutting at theMsglayer keeps those groups intactConsole:
GET /api/chats/{chat_id}?limit=N(include_app_owned=false). N is a Console-local preference (localStorageqwenpaw_chat_history_page_size), default 50, integer 1–10000 to match the backendlimitcontract. It is not stored onchannels.consoledisplay settings (those are outbound IM rendering).before) and replaces the transcript.beforeset to the oldest loadedmetadata.original_id, prepends whenhas_more, and keeps scroll position. Groups sharingoriginal_idare not split. Short chats hide the Load earlier button (has_more=false) but still show the page-size input. Scrolling the transcript to the oldest edge also triggers load-earlier; the button stays as a fallback.@agentscope-ai/chathas no virtual list (itsPAGE_SIZE=10helper still accumulates into a full DOM). The Chat page swaps the vendorMessageListfor a host list that keeps the reverse scroller (column-reverse,scrollTop === 0at the newest edge) and only mounts viewport + overscan rows. Row heights are measured and cached (code blocks, cards, images); there is no fixed row height. New / streaming messages still stick to the bottom; prepending older history does not jump the viewport. Session switch abort, ownership epochs, pending user-message patching, the converted-session LRU, and reverse-list wheel handling are unchanged.limit/before. A window withhas_moreis cached for cheap switch-back but is never treated as the full canonical history.Skills-list gzip from #6635 is not part of this PR.
Related Issue: Relates to #7049, #3915, #6635
Security Considerations:
limitandbeforeonly slice a history the caller can already read through this endpoint; they do not change ownership orinclude_app_owned.limitis clamped (ge=0,le=10000). Conversion runs only on the windowedMsglist. The Console page size is a browser-local integer in that same range.beforeis an opaqueMsg.id/metadata.original_idstring, not a path or query language; an unknown cursor degrades to a plainlimitwindow instead of erroring.total/has_moreare counts of source messages, derived from the same memory list the handler already loaded.Type of Change
Component(s) Affected
Checklist
pre-commit run --all-fileslocally and it passespytestor as relevant) and they passFor Channel Changes (DingTalk, Lark, QQ, Console, etc.)
./scripts/check-channels.sh(or./scripts/check-channels.sh --changed) and it passestests/contract/channels/test_<channel>_contract.py(REQUIRED)create_instance()with proper channel initializationtests/contract/channels/__init__.py)tests/unit/channels/test_<channel>.pyfor complex internal logicTesting
Manual:
GET /api/chats/{chat_id}?limit=50(plusinclude_app_owned=falsefrom the Chat surface). The handler should convert only that window, not the fullMsglist.200(or set it under Settings → Console → Chat). Confirm the value persists across reload.?limit=200(nobefore) and replace the transcript with the newest 200 source messages.beforeequal to the oldest loaded source messageoriginal_idandlimit=200.has_more=false) but can still show the page-size input.getChatwithoutlimit/beforeso matches outside the latest window are found.Evidence
$ pre-commit run --all-files check python ast.........................................................Passed sort simple yaml files...............................(no files to check)Skipped check yaml...............................................................Passed check xml................................................................Passed check toml...............................................................Passed check docstring is first.................................................Passed check json...............................................................Passed fix python encoding pragma...............................................Passed detect private key.......................................................Passed trim trailing whitespace.................................................Passed Add trailing commas......................................................Passed mypy.....................................................................Passed black....................................................................Passed flake8...................................................................Passed pylint...................................................................Passed prettier.................................................................Passed Lint GitHub Actions workflow files.......................................Passed $ pytest tests/unit/app/chats/test_history_window.py \ tests/unit/app/chats/test_api_history_pagination.py \ tests/unit/app/chats/test_api.py \ tests/unit/app/chats/test_models.py -q ............................................. [100%] 45 passed in 0.70s $ npx vitest run src/api/modules/chat.test.ts \ src/pages/Chat/sessionApi/historyWindow.test.ts \ src/pages/Chat/sessionApi/historyPageSize.test.ts \ src/pages/Chat/tests/historyPagination.test.ts \ src/pages/Chat/tests/agentSessionOwnership.test.ts \ src/pages/Chat/components/LoadEarlierMessages/LoadEarlierMessages.test.tsx \ src/pages/Chat/components/HistoryPageSizeInput/index.test.tsx \ src/pages/Settings/Console/index.test.tsx \ src/pages/Chat/tests/pendingUserMessage.test.ts \ src/pages/Chat/tests/sessionCacheStaleness.test.ts \ src/pages/Chat/tests/testLargeSession.test.ts \ src/pages/Chat/ChatPage.coverage.test.tsx \ src/pages/Chat/virtualMessageList/range.test.ts \ src/pages/Chat/virtualMessageList/VirtualizedBubbleList.test.tsx \ src/pages/Chat/messageScroll.test.ts Test Files 15 passed (15) Tests 210 passed (210)Additional Notes
Backend design and tests started from #7049 (@zsrmoyanzsr). Current
mainalso hasinclude_app_ownedon this endpoint; the pagination params sit next to that. This is meant to answer @zhijianma's review on #7049 rather than re-open that branch as-is.No website docs change:
website/public/docsonly lists/api/chats/*. Channel contract checks above are N/A (this is the web Console UI + chats API, not an IM channel). I did not have a 540k-token chat fixture in this environment, so the long-open path is covered by the Msg-layer conversion spy, API pagination tests, and the virtual-list mount/prepend/scroll-to-top tests rather than a live browser pass against a huge session.