"""搜索卡与搜索参数区域的 NiceGUI 视图。""" from typing import Any from nicegui import ui from platform_utils import nsfw_allowed from webui.constants import SEARCH_MODE_OPTIONS def build_search_panel(controller: Any) -> None: with ui.card().classes('w-full product-search-card'): with ui.row().classes('w-full items-start justify-between gap-3 mb-1'): with ui.row().classes('items-center gap-2'): ui.icon('search', size='2em', color='primary') ui.label('Danbooru 标签模糊搜索').classes('text-2xl font-bold text-gray-800') ui.button( '帮助 / 关于', icon='help_outline', on_click=controller.help_dialog.open, ).props('flat dense no-caps color=grey-7').classes('text-sm') ui.label( '基于语义匹配的标签搜索引擎,支持多维匹配与共现关联推荐。' ).classes('text-sm text-gray-500 -mt-1 mb-1') with ui.row().classes( 'w-full items-center justify-between gap-x-4 gap-y-1 flex-wrap mb-3' ): ui.link( '查看工具链介绍 / 使用指南 →', 'http://intro.sakizuki.site/index.html', new_tab=True, ).classes('text-sm text-blue-600 hover:text-blue-800 font-medium') ui.html( '觉得好用?给 ' '' 'Space 点个 Like ❤️,或到 ' '' 'GitHub 点 Star ⭐' ).classes('homepage-support-note') with ui.row().classes('w-full gap-3 items-stretch'): controller.search_input = ui.textarea( placeholder='输入自然语言描述或模糊概念,例如:一个穿着白色水手服的少女在雨中奔跑...' ).classes('flex-grow text-base motion-search-input').props('outlined rows=2') controller.search_input.on('keydown.ctrl.enter', controller.perform_search) with ui.column().classes('justify-center'): controller.search_btn = ui.button( '', on_click=controller.perform_search, icon='search' ).classes('px-6 h-full min-h-16 motion-search-button').props('unelevated color=dark') with controller.search_btn: ui.label('搜索').classes('text-sm mt-1') controller.spinner = ui.spinner(size='2em').classes( 'hidden motion-search-spinner' ) controller.search_params_row = ui.row().classes( 'w-full gap-6 items-center mt-3 flex-wrap' ) with controller.search_params_row: with ui.row().classes('items-center gap-2'): ui.label('搜索模式 (beta)').classes('text-sm text-gray-600') controller.input_search_mode = ui.select( SEARCH_MODE_OPTIONS, value='自定义', ).classes('w-28').props('outlined dense') controller.input_search_mode.on( 'update:model-value', controller._on_search_mode_change ) with ui.tooltip().props('content-class="bg-black text-white shadow-4"'): ui.label('选择模式自动填充对应参数;手动修改参数后自动变为「自定义」').style('font-size:14px;') with ui.row().classes('items-center gap-2'): ui.label('Top K (语义相关)').classes('text-sm text-gray-600') controller.input_top_k = ui.number(value=10, min=1, max=200).classes('w-20') \ .props('outlined dense') controller.input_top_k.on('update:model-value', controller._on_param_changed) with ui.row().classes('items-center gap-2'): ui.label('结果上限').classes('text-sm text-gray-600') controller.input_limit = ui.number(value=80, min=10, max=500).classes('w-20') \ .props('outlined dense') controller.input_limit.on('update:model-value', controller._on_param_changed) with ui.switch('显示 NSFW(成人) 内容', value=False).props('color=red') as nsfw_switch: if not nsfw_allowed(): with ui.tooltip().props('content-class="bg-black text-white shadow-4"'): ui.label('NSFW 内容在当前平台不可用').style('font-size:14px;') controller.input_nsfw = nsfw_switch if not nsfw_allowed(): controller.input_nsfw.disable() else: controller.input_nsfw.on('update:model-value', controller.on_nsfw_toggle) with ui.switch('智能分词', value=True).props('color=primary') as segment_switch: with ui.tooltip().props('content-class="bg-black text-white shadow-4"'): ui.label('关闭后系统将只匹配完整句子,适用于精准搜索整句。').style('font-size:14px;') controller.input_segment = segment_switch controller.input_segment.on('update:model-value', controller._on_param_changed) controller.advanced_options = ui.expansion('高级选项', icon='tune').classes('w-full mt-2') with controller.advanced_options: with ui.column().classes('w-full p-3 gap-4'): with ui.row().classes('items-center gap-2'): ui.label('热度权重').classes('text-sm font-bold text-gray-700') controller.input_weight = ui.slider( min=0.0, max=1.0, value=0.15, step=0.05, ).classes('w-40') ui.label().bind_text_from( controller.input_weight, 'value', lambda value: f'{value:.2f}', ).classes('text-sm font-mono text-gray-700 w-8') controller.input_weight.on('update:model-value', controller._on_param_changed) with ui.row().classes('w-full gap-8 flex-wrap'): with ui.column().classes('gap-2'): ui.label('匹配层筛选').classes('font-bold text-sm text-gray-700') display_map = { '英文': '英文标签', '中文扩展词': '中文扩展词', '释义': '维基释义', '中文核心词': '中文核心词', 'artist': 'artist', } for layer in ['英文', '中文扩展词', '释义', '中文核心词', 'artist']: checkbox = ui.checkbox( display_map.get(layer, layer), value=True, on_change=lambda event, current=layer: controller.selected_layers.__setitem__(current, event.value), ).props('color=primary dense') controller._layer_checkboxes[layer] = checkbox with ui.column().classes('gap-2'): ui.label('类型筛选').classes('font-bold text-sm text-gray-700') color_map = {'General': 'blue', 'Copyright': 'purple', 'Character': 'green'} label_map = { 'General': '通用 (General)', 'Copyright': '作品 (Copyright)', 'Character': '角色 (Character)', } for category in ['General', 'Copyright', 'Character']: checkbox = ui.checkbox( label_map[category], value=True, on_change=lambda event, current=category: controller.selected_cats.__setitem__(current, event.value), ).props(f'color={color_map[category]} dense') controller._cat_checkboxes[category] = checkbox with ui.column().classes('gap-2'): ui.label('表格显示列').classes('font-bold text-sm text-gray-700') controller.sw_semantic = ui.switch('显示语义分', value=False) controller.sw_layer = ui.switch('显示匹配层', value=False) controller.sw_source = ui.switch('显示匹配来源', value=False) controller.sw_semantic.on('update:model-value', controller._update_table_columns) controller.sw_layer.on('update:model-value', controller._update_table_columns) controller.sw_source.on('update:model-value', controller._update_table_columns) with ui.column().classes('gap-2'): ui.label('标签分组模式').classes('font-bold text-sm text-gray-700') controller.input_group_mode = ui.select( ['off', 'expand', 'diverse'], value='off', ).classes('w-40').props('outlined dense') with ui.tooltip().props('content-class="bg-black text-white shadow-4"'): ui.label('off=关闭 | expand=同类召回增强 | diverse=多样性约束').style('font-size:14px;') controller.input_group_mode.on( 'update:model-value', controller._on_param_changed ) controller.input_max_per_group = ui.number( value=2, min=1, max=10, ).classes('w-20').props('outlined dense') ui.label('每组最大标签数(diverse 模式)').classes('text-xs text-gray-500') controller.input_max_per_group.on( 'update:model-value', controller._on_param_changed ) with ui.element('div').classes('w-full border-t border-slate-100 pt-3 mt-1'): controller.service_status_container = ui.column().classes('w-full gap-0') controller._update_service_status() controller._start_service_status_task()