fix(dashboard): drop stale exclude_ids hardcode hiding conversations (#10129)

The dashboard conversation pages hardcoded exclude_ids="astrbot", and
the backend expanded each entry into `user_id NOT LIKE '<id>%'`. Since
user_id is the UMO "<platform_id>:<message_type>:<session_id>", every
conversation of a platform whose adapter id is "astrbot" (or any id
starting with it) was filtered out and the list showed 0 items (#10126).

The parameter never matched the built-in WebChat sessions it presumably
targeted (their UMO starts with "webchat:"), so it has had no functional
effect since it was introduced; remove it instead of replacing it.

Tighten the remaining exclude_ids semantics for API callers: match only
the exact user_id or its platform segment ("id:...") and escape LIKE
wildcards, so "astrbot" no longer swallows "astrbotweb".

Fixes #10126
This commit is contained in:
Ruochen Pan
2026-09-19 10:43:59 +08:00
committed by GitHub
parent f99c76ec10
commit bb79c083e3
4 changed files with 67 additions and 9 deletions
+16 -1
View File
@@ -433,8 +433,23 @@ class SQLiteDatabase(BaseDatabase):
conditions.append(col(ConversationV2.platform_id).in_(platforms))
exclude_ids = kwargs.get("exclude_ids") or []
for exclude_id in exclude_ids:
# Match the whole UMO or its platform segment only, so an id
# like "astrbot" does not swallow platforms such as
# "astrbotweb". Escape LIKE wildcards inside the id itself.
escaped = (
exclude_id.replace("\\", "\\\\")
.replace("%", r"\%")
.replace("_", r"\_")
)
conditions.append(
not_(col(ConversationV2.user_id).like(f"{exclude_id}%"))
not_(
or_(
col(ConversationV2.user_id) == exclude_id,
col(ConversationV2.user_id).like(
f"{escaped}:%", escape="\\"
),
)
)
)
exclude_platforms = kwargs.get("exclude_platforms") or []
if exclude_platforms:
-1
View File
@@ -802,7 +802,6 @@ export default {
params.search = search;
}
params.exclude_ids = 'astrbot';
params.include_history = false;
const response = await conversationApi.list(params, {
@@ -355,11 +355,7 @@ async function fetchConversations() {
group_by_session: groupBySession.value,
};
if (keyword.value.trim()) params.keyword = keyword.value.trim();
if (umoQuery.value.trim()) {
params.umo = umoQuery.value.trim();
} else {
params.exclude_ids = "astrbot";
}
if (umoQuery.value.trim()) params.umo = umoQuery.value.trim();
if (selectedBotIds.value.length) {
params.platforms = selectedBotIds.value.join(",");
}
+50 -2
View File
@@ -60,6 +60,22 @@ async def test_filtered_conversations_summary_skips_content_and_applies_filters(
created_at=datetime(2023, 1, 1, tzinfo=timezone.utc),
updated_at=datetime(2023, 1, 1, tzinfo=timezone.utc),
),
ConversationV2(
conversation_id="astrbotweb",
platform_id="astrbotweb",
user_id="astrbotweb:GroupMessage:9",
content=[{"role": "assistant", "content": "kept"}],
created_at=datetime(2025, 6, 1, tzinfo=timezone.utc),
updated_at=datetime(2025, 6, 1, tzinfo=timezone.utc),
),
ConversationV2(
conversation_id="astrbot-legacy",
platform_id="astrbot",
user_id="astrbot",
content=[{"role": "assistant", "content": "excluded"}],
created_at=datetime(2022, 1, 1, tzinfo=timezone.utc),
updated_at=datetime(2022, 1, 1, tzinfo=timezone.utc),
),
]
async with db.get_db() as session:
async with session.begin():
@@ -74,10 +90,11 @@ async def test_filtered_conversations_summary_skips_content_and_applies_filters(
exclude_platforms=["webchat"],
)
assert total == 3
assert total == 4
assert [item.conversation_id for item in summary] == [
"friend",
"group",
"astrbotweb",
"other",
]
assert all("content" in sqlalchemy_inspect(item).unloaded for item in summary)
@@ -96,6 +113,33 @@ async def test_filtered_conversations_summary_skips_content_and_applies_filters(
assert all(item.history == "[]" for item in manager_summary)
assert all(json.loads(item.history) == [] for item in manager_summary)
# exclude_ids must match only the exact user_id or its platform segment,
# so "astrbot" does not swallow "astrbotweb", while a colon-less legacy
# user_id "astrbot" is still matched exactly.
segment_matches, segment_total = await db.get_filtered_conversations(
page=1,
page_size=10,
include_history=False,
exclude_ids=["astrbot"],
)
assert segment_total == 5
assert [item.conversation_id for item in segment_matches] == [
"friend",
"group",
"astrbotweb",
"other",
"webchat",
]
# LIKE wildcards inside an exclude id must stay literal.
_, wildcard_total = await db.get_filtered_conversations(
page=1,
page_size=10,
include_history=False,
exclude_ids=["%"],
)
assert wildcard_total == 7
title_matches, _ = await db.get_filtered_conversations(
search_query="中文标题",
include_history=False,
@@ -119,7 +163,7 @@ async def test_filtered_conversations_summary_skips_content_and_applies_filters(
assert keyword_does_not_match_umo == []
full, full_total = await db.get_filtered_conversations(page_size=10)
assert full_total == 5
assert full_total == 7
assert all("content" not in sqlalchemy_inspect(item).unloaded for item in full)
umo_matches, _ = await db.get_filtered_conversations(
@@ -135,14 +179,18 @@ async def test_filtered_conversations_summary_skips_content_and_applies_filters(
include_history=False,
)
assert [item.conversation_id for item in updated_ascending] == [
"astrbot-legacy",
"astrbot",
"webchat",
"other",
"astrbotweb",
"group",
"friend",
]
assert await db.get_conversation_platform_ids() == [
"astrbot",
"astrbotweb",
"qq",
"telegram",
"webchat",