"""推荐区域共享的状态与调度规则,不包含 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)