Skip to content

fix: paginate WebChat project sessions consistently with the sidebar - #10488

Open
wcqqq1214 wants to merge 4 commits into
AstrBotDevs:masterfrom
wcqqq1214:fix/project-session-pagination
Open

wcqqq1214 wants to merge 4 commits into
AstrBotDevs:masterfrom
wcqqq1214:fix/project-session-pagination

Conversation

@wcqqq1214

@wcqqq1214 wcqqq1214 commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #10279, applying the same pagination behavior to WebChat project sessions.

Projects currently stop at 100 sessions. This change loads 30 at a time in both the sidebar and project page, fetching older sessions within 120px of the list bottom and using the total count to determine completion.

Modifications / 改动点

  • Preserve the legacy API response and share pagination state between both views, independently per project.

  • Handle retries, stale requests, deduplication, and refreshes after mutations. Skip projects above the visible sidebar to prevent continuous offscreen loading.

  • Update English/Chinese WebUI instructions and regenerate API documentation/client. Navigation and layout are unchanged; existing documentation screenshots remain applicable.

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果

  • 29 Python tests and 17 frontend tests passed, including exact-page boundaries, retries, project isolation, and offscreen-loading regression coverage.
  • Ruff, dashboard build (including type checking), and documentation build passed. The local dashboard build used verify-deps-before-run=false for an existing dependency metadata mismatch.
  • Verified with 125 mock project sessions: both views reach the last session. Scrolling to ordinary conversations leaves the offscreen project at 30 loaded sessions; scrolling back loads the next page.

Checklist / 检查清单

  • 😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
    / 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。

  • 👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
    / 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”。

  • 📚 I checked the affected WebUI instructions and screenshots in docs/zh and docs/en against the changed navigation, page structure, and labels, and updated them in this PR (or explained why no documentation update is needed). For renamed, moved, or merged entry points, I included an old entry → new entry mapping in the documentation and changelog.
    / 我已对照变化后的 WebUI 入口、页面结构和术语,核对并在本 PR 中更新 docs/zh 和 docs/en 的相关操作说明与截图(或说明无需更新文档的原因)。入口改名、移动或合并时,已在文档和 changelog 中补充 旧入口 → 新入口 对照。

  • 🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
    / 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到 requirements.txt 和 pyproject.toml 文件相应位置。

  • 😮 My changes do not introduce malicious code.
    / 我的更改没有引入恶意代码。

Summary by Sourcery

Paginate WebChat project sessions consistently across the sidebar and project page while retaining backward compatibility.

New Features:

  • Add paginated project-session retrieval with total counts while preserving the legacy unpaginated API response.
  • Load project sessions incrementally in both the sidebar and project view, including automatic near-bottom loading and retry handling.

Bug Fixes:

  • Allow projects with more than 100 sessions to reach older conversations consistently across both views.
  • Prevent stale, duplicate, offscreen, and cross-project session loads from corrupting pagination state.

Enhancements:

  • Share independent per-project session caches and pagination state between the sidebar and project page.
  • Refresh loaded project-session pages after mutations and add an index to improve project-session relation lookups.

Documentation:

  • Document paginated project-session loading and update the related API specifications and generated client types.

Tests:

  • Add backend and frontend coverage for pagination boundaries, retries, request invalidation, project isolation, mutation refreshes, indexing, and viewport loading behavior.

@wcqqq1214
wcqqq1214 marked this pull request as ready for review October 10, 2026 20:50

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 4 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="astrbot/core/db/__init__.py" line_range="971-987" />
<code_context>
         ...

+    @abc.abstractmethod
+    async def get_project_sessions_paginated(
+        self,
+        project_id: str,
+        page: int = 1,
+        page_size: int = 100,
+    ) -> tuple[list[PlatformSession], int]:
+        """Get a page of project sessions and its total count.
+
+        Args:
+            project_id: Project whose sessions should be listed.
+            page: One-based page number.
+            page_size: Maximum number of sessions to return.
+
+        Returns:
+            The requested sessions and total number of sessions in the project.
+        """
+        ...
+
     @abc.abstractmethod
</code_context>
<issue_to_address>
**Existing adapters cannot instantiate**

When an external adapter implements the old abstract interface, `BaseDatabase` still marks the new method abstract, so the adapter remains abstract and construction raises `TypeError` before pagination can run.

Provide a concrete backward-compatible implementation or otherwise avoid making this method a required abstract method.
</issue_to_address>

### Comment 2
<location path="dashboard/src/components/chat/ProjectList.vue" line_range="257-263" />
<code_context>
   if (
     !container ||
     container.clientHeight === 0 ||
</code_context>
<issue_to_address>
**Visible projects load prematurely**

When an expanded project list is visible but ends more than 120px above the sidebar viewport bottom, `loadMoreSessions` treats a negative `listBottom - bottom` as within the 120px threshold, so scrolling loads another page for expanded project lists that have already ended well above the viewport bottom. The pagination watcher repeats this as pages finish, causing unnecessary requests and loading sessions before users approach those lists.

Require the list bottom to be within the intended near-bottom range, such as `listBottom >= bottom - 120`, as well as no more than 120px below it.
</issue_to_address>

### Comment 3
<location path="astrbot/core/db/sqlite.py" line_range="2517" />
<code_context>
                     == col(SessionProjectRelation.session_id),
                 )
                 .where(col(SessionProjectRelation.project_id) == project_id)
-                .order_by(desc(PlatformSession.updated_at))
+            )
</code_context>
<issue_to_address>
**Project pagination scans every relation**

When the database contains many session-project relations, `get_project_sessions_paginated` filters `session_project_relations` by `project_id` and runs both a count and a page query on each request, but the relation table has only a unique index on `session_id`. As the relation table grows, scrolling through project sessions repeatedly scans it, making pagination increasingly slow.

Add an index on `SessionProjectRelation.project_id` so the count and page queries can locate a project's relations without scanning the whole table.
</issue_to_address>

### Comment 4
<location path="dashboard/src/api/v1.ts" line_range="942" />
<code_context>
     );
   },
-  listProjectSessions(projectId: string) {
+  listProjectSessions(projectId: string, params?: { page: number; page_size: number }) {
     return typed<any>(
-      openApiV1.listChatProjectSessions({ path: { project_id: projectId } }),
</code_context>
<issue_to_address>
**Valid pagination queries rejected by types**

When a typed caller wants to set only `page` or only `page_size`, `listProjectSessions` requires both `page` and `page_size` whenever `params` is supplied, so typed consumers cannot request either parameter alone even though the HTTP endpoint accepts either one; TypeScript reports a compile-time error for those valid requests.

Make both properties optional in the wrapper parameter type.
</issue_to_address>

Sourcery assessment

Approval pending. 4 findings to address first.

Blocking findings: astrbot/core/db/__init__.py:987, dashboard/src/components/chat/ProjectList.vue:263, astrbot/core/db/sqlite.py:2517, dashboard/src/api/v1.ts:942


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment on lines +971 to +987
async def get_project_sessions_paginated(
self,
project_id: str,
page: int = 1,
page_size: int = 100,
) -> tuple[list[PlatformSession], int]:
"""Get a page of project sessions and its total count.

Args:
project_id: Project whose sessions should be listed.
page: One-based page number.
page_size: Maximum number of sessions to return.

Returns:
The requested sessions and total number of sessions in the project.
"""
...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Existing adapters cannot instantiate

When an external adapter implements the old abstract interface, BaseDatabase still marks the new method abstract, so the adapter remains abstract and construction raises TypeError before pagination can run.

Provide a concrete backward-compatible implementation or otherwise avoid making this method a required abstract method.

Prompt for AI agents
In `astrbot/core/db/__init__.py` at lines 971-987:

**Existing adapters cannot instantiate**

When an external adapter implements the old abstract interface, `BaseDatabase` still marks the new method abstract, so the adapter remains abstract and construction raises `TypeError` before pagination can run.

Provide a concrete backward-compatible implementation or otherwise avoid making this method a required abstract method.

Comment on lines +257 to +263
if (
!isProjectExpanded(projectId) ||
!pagination?.hasMore ||
pagination.loading ||
pagination.error ||
listBottom <= top ||
listBottom - bottom > 120

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Medium · Visible projects load prematurely

When an expanded project list is visible but ends more than 120px above the sidebar viewport bottom, loadMoreSessions treats a negative listBottom - bottom as within the 120px threshold, so scrolling loads another page for expanded project lists that have already ended well above the viewport bottom. The pagination watcher repeats this as pages finish, causing unnecessary requests and loading sessions before users approach those lists.

Require the list bottom to be within the intended near-bottom range, such as listBottom >= bottom - 120, as well as no more than 120px below it.

Prompt for AI agents
In `dashboard/src/components/chat/ProjectList.vue` at lines 257-263:

**Visible projects load prematurely**

When an expanded project list is visible but ends more than 120px above the sidebar viewport bottom, `loadMoreSessions` treats a negative `listBottom - bottom` as within the 120px threshold, so scrolling loads another page for expanded project lists that have already ended well above the viewport bottom. The pagination watcher repeats this as pages finish, causing unnecessary requests and loading sessions before users approach those lists.

Require the list bottom to be within the intended near-bottom range, such as `listBottom >= bottom - 120`, as well as no more than 120px below it.

Comment thread astrbot/core/db/sqlite.py
Comment thread dashboard/src/api/v1.ts Outdated
@wcqqq1214

Copy link
Copy Markdown
Contributor Author

@sourcery-ai review

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="openspec/openapi-v1.yaml" line_range="1560" />
<code_context>
     get:
       tags: [Chat]
       summary: List sessions in a ChatUI project
+      description: Without pagination parameters, returns the legacy list of up to 100 sessions. With page or page_size, returns sessions, total, page, and page_size.
       operationId: listChatProjectSessions
       x-astrbot-scope: chat
</code_context>
<issue_to_address>
**Generated clients cannot read page fields**

When a consumer uses the generated client to read a paginated response, the OpenAPI description promises `sessions`, `total`, `page`, and `page_size`, but the response remains the generic `SuccessEnvelope` with `data: unknown`; generated-client consumers cannot access those fields without narrowing or casting, so the new response contract is not usable as typed data.

Define and reference a response schema that describes both the legacy and paginated response shapes, or provide a typed pagination-specific response.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. If the pagination or counting query is wrong, users could see incomplete or misordered project sessions, but reverting restores the previous behavior. The added database index remains after a code revert and would need a separate cleanup if it proved problematic, though that is bounded and straightforward.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨

Comment thread openspec/openapi-v1.yaml
get:
tags: [Chat]
summary: List sessions in a ChatUI project
description: Without pagination parameters, returns the legacy list of up to 100 sessions. With page or page_size, returns sessions, total, page, and page_size.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚪ Low · Generated clients cannot read page fields

When a consumer uses the generated client to read a paginated response, the OpenAPI description promises sessions, total, page, and page_size, but the response remains the generic SuccessEnvelope with data: unknown; generated-client consumers cannot access those fields without narrowing or casting, so the new response contract is not usable as typed data.

Define and reference a response schema that describes both the legacy and paginated response shapes, or provide a typed pagination-specific response.

Prompt for AI agents
In `openspec/openapi-v1.yaml` at line 1560:

**Generated clients cannot read page fields**

When a consumer uses the generated client to read a paginated response, the OpenAPI description promises `sessions`, `total`, `page`, and `page_size`, but the response remains the generic `SuccessEnvelope` with `data: unknown`; generated-client consumers cannot access those fields without narrowing or casting, so the new response contract is not usable as typed data.

Define and reference a response schema that describes both the legacy and paginated response shapes, or provide a typed pagination-specific response.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant