Skip to content

Feature(aidp): Knowledge base details page - #4096

Open
mew-boo wants to merge 6 commits into
developfrom
st/aidp-knowledge-base-details
Open

mew-boo wants to merge 6 commits into
developfrom
st/aidp-knowledge-base-details

Conversation

@mew-boo

@mew-boo mew-boo commented Oct 14, 2026

Copy link
Copy Markdown

AIDP knowledge base details page

Changes

  1. The File tab is renamed as Uploaded Files.
  2. The function of deleting files in batches is added to the Uploaded Files tab. A maximun of 10 files can be deleted at a time.
  3. The Upload Task tab is added.
  4. The Details tab is added.

Screenshots
image
image
image

@cj2026-bit cj2026-bit left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I verified the detail page against the deploy worktree and its AIDP mock. The inline comments below describe reproducible behavior and places where the mock diverges from the AIDP response contract previously observed.

server_url,
api_key,
channel["fs_id"],
f"/{kds_id}",

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The selected channel includes its source directory, but this request constructs /{kds_id} instead. I reproduced this on the PR commit: the Upload Tasks tab showed 0 rows; the backend sent /aidp-kb-faq, which the mock logged as outside the KB and returned empty. Posting History with the channel src_dir (/aidp/knowledge/aidp-kb-faq) returned 10 rows (22 total). Please pass the selected channel's actual source directory here.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

fixed

)
raw_items = history.get("value") if isinstance(history, dict) else None
rows = [item for item in raw_items if isinstance(item, dict)] if isinstance(raw_items, list) else []
raw_total = history.get("total_count") if isinstance(history, dict) else None

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The AIDP History response contract provided for this integration uses total_count for the current page and total_record_count for the global total (for example, 3 vs. 317). Marking the page-local value as reliable makes AidpPagination treat 3 as the global total, so it can disable next-page navigation even when next_link is present. The included mock returns a filtered all-pages count here, which masks the mismatch. Please use the AIDP paging signal without treating total_count as a global total.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fixed.

"extracting": _history_count("processing_record_count"),
"failed": _history_count("failed_record_count"),
"success": _history_count("success_record_count"),
"queued": _history_count("queued_record_count"),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The observed AIDP History payload exposes waiting_record_count, while this adapter reads queued_record_count. On the real response the queued summary will therefore stay at zero when files are waiting. The PR mock emits queued_record_count, so it currently hides this mismatch. Please map the actual AIDP field to the UI's queued statistic.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fixed.

lambda: client.post(
retry_url,
headers=headers,
json={"file_uuids": file_uuids},

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The AIDP Retry contract previously tested for this integration requires retry_file_info: [{"file_uuid": "..."}]; sending file_uuids returns HTTP 422 because retry_file_info is missing. The PR mock accepts file_uuids, so local success would not catch the live failure. Please align both the adapter and mock with AIDP's request body.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fixed.

lane="control-io",
owner="config",
),
return_exceptions=True,

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

GET /knowledge-bases/{kds_id}/documents already exists with the same page, page_size, and keyword parameters and calls Count. This new /files endpoint duplicates that path, but return_exceptions=True turns a Count failure into HTTP 200 with an estimated total. That hides the request failure and can show a misleading total. Please reuse the existing documents endpoint (or move the needed changes there) and let Count failures fail the request.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Removed.

{formatDateTime(detail.updated_at)}
</AidpDetailField>
<AidpDetailField label={t("aidpKnowledge.detailCreator")}>
{detail.created_by || detail.user_name || AIDP_UNKNOWN_VALUE}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

created_by is the Nexent permission record's owner_user_id; the API also provides creator_name for display. Prioritizing created_by makes this field show an opaque ID -- I reproduced the detail page displaying the literal user_id -- instead of the Nexent user name. Please render creator_name, falling back to -- when it is absent, rather than showing the ID.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fixed.

@cj2026-bit cj2026-bit left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The PR checks currently fail on two actionable issues: changed files are missing the required copyright header, and the updated unavailable-KB test expects HTTP 200 although the handler still calls Count after the detail request fails. Inline comments identify both.

from __future__ import annotations

import asyncio
import json

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The copyright-check gate reports this file and 22 other changed files as missing the required copyright header. Please add the standard header to all 23 files reported by the check; otherwise this required PR gate continues to fail.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fixed.

# The detail view degrades instead of failing: the count fetch only
# runs for ACTIVE knowledge bases, so a downed AIDP detail + count no
# longer escalates into a 5xx detail response.
assert response.status_code == HTTPStatus.OK

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This new expectation does not match the handler: after the detail call fails, get_knowledge_base still calls count_aidp_docs_impl unconditionally. With this test mocked Count failure, that exception escapes and the endpoint returns 502, which is the CI failure. Please either skip Count when the KB is already marked UNAVAILABLE, so the expected 200/no-count contract is real, or change the test to assert the intended failure behavior.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fixed.

@cj2026-bit cj2026-bit self-assigned this Oct 15, 2026

@cj2026-bit cj2026-bit left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Rechecked the eight previous review comments against c5700e3. Six are addressed; the pagination comment and the duplicated /files-path comment still need the two corrections below. The two backend test modules pass locally (422 tests), but the isolated regression probes below expose behavior those tests do not cover.

Comment on lines +1255 to +1257
raw_global_total = history.get("total_record_count") if isinstance(history, dict) else None
total_reliable = isinstance(raw_global_total, int) and not isinstance(raw_global_total, bool)
total = int(raw_global_total) if total_reliable else (page - 1) * page_size + len(rows)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Keep the global History statistic out of filtered pagination

This fixes the page-local total_count problem for the unfiltered list, but total_record_count is also used as a reliable total after keyword/status filtering. The PR mock computes this statistic before applying either filter. AidpPagination ignores hasMore when totalReliable is true, so a filtered last page can still expose many empty pages.

Reproduced against c5700e3 with an isolated History response: one matching row, total_record_count=317, next_link=null. The endpoint returns total_count=317, total_reliable=true, has_more=false; rendering the actual pagination component produces 32 pages with Next enabled.

For filtered queries, please use total_reliable=false and the upstream paging signal unless AIDP supplies a confirmed filtered total. Keep total_record_count in the summary statistics. Add a regression where one filtered match and no next_link leave Next disabled.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Revised, please check if it meets expectations.

Comment on lines +1197 to +1198
if len(rows) < page_size:
has_more = False

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[P2] Do not override an explicit next_link just because the page is short

The /files route is still present and used by listIngestedFiles. Count failures now propagate, but this remaining branch makes its paging behavior differ from /documents: a short page unconditionally clears has_more even when AIDP explicitly supplies the next-page link.

Reproduced against c5700e3 using the same isolated upstream response for both routes: page_size=10, two rows, next_link="/next-page", keyword search. /files returns has_more=false, while /documents returns true. The detail page therefore prevents users from reaching the remaining search results.

Please reuse the existing /documents paging implementation, or infer the last page from row count only when no explicit paging signal is available. The test test_file_list_suppresses_phantom_next_link_on_short_page currently asserts this truncation as correct; change it to protect the upstream next_link instead.

…into develop_st

Pull the latest code to resolve the issue that the automatic test case fails
…am next_link and stop short-page override in /files
@codecov

codecov Bot commented Oct 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.75648% with 41 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ckend/ext_components/aidp/services/aidp_service.py 58.00% 21 Missing ⚠️
backend/ext_components/aidp/apps/aidp_mgmt_app.py 86.01% 14 Missing and 6 partials ⚠️

📢 Thoughts on this report? Let us know!

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.

3 participants