DanbooruSearch / webui /recommendations.py
SAkizuki's picture
Auto-sync from GitHub Actions
65b0b93 verified
Raw
History Blame Contribute Delete
5.27 kB
"""推荐区域共享的状态与调度规则,不包含 NiceGUI 渲染。"""
import asyncio
from collections.abc import Awaitable, Callable
from typing import Any
def recommendation_seed_tags(controller: Any, selected_tags: list[str]) -> list[str]:
"""排除画师标签,避免把画师作为关联/同类推荐的种子。"""
artist_tags = (
set(controller._current_artist_rec_tags)
| set(controller._artist_result_tags)
| set(controller._workspace_artist_tags)
)
if controller.result_table is not None:
for row in controller.result_table.rows:
if row.get('layer') == 'artist' and row.get('tag'):
artist_tags.add(row['tag'])
return [tag for tag in selected_tags if tag not in artist_tags]
def merge_related_results(
current_related: list,
related: list | None,
selected_tags: set[str],
) -> list:
"""刷新时保留仍被选中的旧关联标签,防止已选项从面板消失。"""
related = list(related or [])
new_tags = {item.tag for item in related}
preserved = [
item for item in current_related
if item.tag in selected_tags and item.tag not in new_tags
]
return related + preserved
def queue_latest_recommendation_request(
controller: Any,
selected_tags: list[str],
show_nsfw: bool,
scopes: set[str],
) -> bool:
"""合并同一选择快照的范围;返回是否需要启动消费任务。"""
selected_tags = recommendation_seed_tags(controller, selected_tags)
requested_scopes = frozenset(scopes)
pending = controller._pending_recommendation_request
if (
pending is not None
and pending['selected_tags'] == selected_tags
and pending['show_nsfw'] == show_nsfw
):
requested_scopes = pending['scopes'] | requested_scopes
controller._recommendation_generation += 1
controller._pending_recommendation_request = {
'generation': controller._recommendation_generation,
'selected_tags': list(selected_tags),
'show_nsfw': show_nsfw,
'scopes': requested_scopes,
}
return controller._recommendation_task is None or controller._recommendation_task.done()
def page_count(item_count: int, page_size: int) -> int:
"""返回分页数量;空列表没有有效页。"""
if item_count <= 0 or page_size <= 0:
return 0
return (item_count + page_size - 1) // page_size
def clamp_page(page: int, total_pages: int) -> int:
"""把外部页码约束在有效范围内。"""
return max(1, min(page, total_pages)) if total_pages > 0 else 0
def page_items(items: list, page: int, page_size: int) -> list:
"""返回有效页中的项目,空结果或无效页都返回空列表。"""
total_pages = page_count(len(items), page_size)
current_page = clamp_page(page, total_pages)
if current_page == 0:
return []
start = (current_page - 1) * page_size
return items[start:start + page_size]
def set_paginated_recommendation_page(
controller: Any,
page: int,
*,
state_prefix: str,
render_page: Callable[[], None],
motion_element_id: str,
) -> None:
"""切换关联或画师推荐页,并同步翻页按钮的状态。"""
page_count_value = getattr(controller, f'{state_prefix}_page_count')
if page_count_value < 1:
return
previous_page = getattr(controller, f'{state_prefix}_page')
current_page = clamp_page(page, page_count_value)
setattr(controller, f'{state_prefix}_page', current_page)
render_page()
motion_class = (
'motion-recommendation-enter-right'
if current_page >= previous_page
else 'motion-recommendation-enter-left'
)
controller._replay_motion(motion_element_id, motion_class)
label = getattr(controller, f'{state_prefix}_page_label')
if label is not None:
label.text = f'{current_page} / {page_count_value}'
previous_button = getattr(controller, f'{state_prefix}_prev_button')
if previous_button is not None:
(previous_button.disable if current_page == 1 else previous_button.enable)()
next_button = getattr(controller, f'{state_prefix}_next_button')
if next_button is not None:
(next_button.disable if current_page == page_count_value else next_button.enable)()
async def consume_latest_recommendation_requests(
controller: Any,
*,
debounce_seconds: float,
fetch: Callable[[dict], Awaitable[dict]],
apply: Callable[[dict, dict], Awaitable[None]],
client_alive: Callable[[], bool],
report_error: Callable[[Exception], None],
) -> None:
"""顺序消费最新快照,计算完成后丢弃已经过期的结果。"""
while controller._pending_recommendation_request is not None:
await asyncio.sleep(debounce_seconds)
request = controller._pending_recommendation_request
controller._pending_recommendation_request = None
try:
result = await fetch(request)
except Exception as exc:
report_error(exc)
continue
if request['generation'] != controller._recommendation_generation:
continue
if not client_alive():
return
await apply(request, result)