feat: enhance visibility management for client options and image analysis based on backend selection

This commit is contained in:
myhloli
2026-06-10 00:28:10 +08:00
parent 1ac0950796
commit 2fed71096a
3 changed files with 107 additions and 39 deletions
+47 -39
View File
@@ -852,11 +852,23 @@ def is_image_analysis_option_visible(backend, effort=DEFAULT_HYBRID_EFFORT):
return False
def is_ocr_options_visible(backend):
"""判断 OCR 语言和强制 OCR 选项是否展示;Hybrid/VLM 已不需要用户指定语言"""
def is_ocr_language_option_visible(backend: object) -> bool:
"""判断 OCR 语言选项是否展示;lang 参数只对 pipeline 后端生效"""
return backend == "pipeline"
def is_force_ocr_option_visible(backend: object) -> bool:
"""判断强制 OCR 开关是否展示;Hybrid 不需要 lang,但仍支持强制 OCR。"""
if not isinstance(backend, str):
return False
return backend == "pipeline" or backend.startswith("hybrid")
def frontend_managed_initial_visibility(is_visible: bool):
"""转换前端托管显隐控件的初始状态;hidden 会保留 DOM 挂载,避免后续重新挂载。"""
return True if is_visible else "hidden"
def should_use_client_side_output_generation(client_side_output_generation):
"""判断当前 Gradio 任务是否需要在客户端生成最终输出。"""
return client_side_output_generation
@@ -1729,22 +1741,25 @@ def main(ctx,
def get_backend_info(backend_choice):
return i18n(select_backend_info_key(backend_choice))
# 更新界面函数
def update_interface(backend_choice, effort_choice):
def build_interface_updates(backend_choice, effort_choice):
"""构建 Gradio 后端联动更新,保证所有事件复用同一套显隐规则。"""
formula_label_update = gr.update(label=get_formula_label(backend_choice), info=get_formula_info(backend_choice))
backend_info_update = gr.update(info=get_backend_info(backend_choice))
effort_update = gr.update(visible=is_effort_option_visible(backend_choice))
ocr_options_update = gr.update(visible=is_ocr_options_visible(backend_choice))
ocr_language_options_update = gr.update(visible=is_ocr_language_option_visible(backend_choice))
force_ocr_update = gr.update(visible=is_force_ocr_option_visible(backend_choice))
return ocr_options_update, formula_label_update, backend_info_update, effort_update
return (
ocr_language_options_update,
force_ocr_update,
formula_label_update,
backend_info_update,
effort_update,
)
def update_client_options_visibility(backend_choice):
"""更新服务器地址控件显隐,避免隐藏 Row 首次切换到 http-client 时挂载失败"""
return gr.update(visible=is_http_client_backend(backend_choice))
def update_image_analysis_visibility(backend_choice, effort_choice):
"""仅更新图片分析控件显隐;实际开关由 Hybrid 后端兜底处理。"""
return gr.update(visible=is_image_analysis_option_visible(backend_choice, effort_choice))
def update_interface(backend_choice, effort_choice):
"""更新可由 Gradio 稳定管理的基础界面项,易重挂载的控件交给前端状态类处理"""
return build_interface_updates(backend_choice, effort_choice)
del kwargs
_gradio_local_api_server.configure(
@@ -1818,8 +1833,12 @@ def main(ctx,
label=i18n("backend"),
value=preferred_option,
info=get_backend_info(preferred_option),
elem_classes=["mineru-backend-select"],
)
with gr.Row(visible=is_http_client_backend(preferred_option)) as client_options:
with gr.Row(
visible=frontend_managed_initial_visibility(is_http_client_backend(preferred_option)),
elem_classes=["mineru-client-options"],
) as client_options:
url = gr.Textbox(
label=i18n("server_url"),
value='http://localhost:30000',
@@ -1925,8 +1944,11 @@ def main(ctx,
image_analysis = gr.Checkbox(
label=i18n("image_analysis_enable"),
value=True,
visible=is_image_analysis_option_visible(preferred_option, DEFAULT_HYBRID_EFFORT),
visible=frontend_managed_initial_visibility(
is_image_analysis_option_visible(preferred_option, DEFAULT_HYBRID_EFFORT)
),
info=i18n("image_analysis_info"),
elem_classes=["mineru-image-analysis-option"],
)
hybrid_effort = gr.Radio(
list(HYBRID_EFFORT_CHOICES),
@@ -1934,15 +1956,21 @@ def main(ctx,
value=DEFAULT_HYBRID_EFFORT,
visible=is_effort_option_visible(preferred_option),
info=i18n("hybrid_effort_info"),
elem_classes=["mineru-hybrid-effort"],
)
with gr.Group(visible=is_ocr_options_visible(preferred_option)) as ocr_options:
with gr.Group(visible=is_ocr_language_option_visible(preferred_option)) as ocr_language_options:
language = gr.Dropdown(
all_lang,
label=i18n("ocr_language"),
value='ch (Chinese, English, Chinese Traditional)',
info=i18n("ocr_language_info"),
)
is_ocr = gr.Checkbox(label=i18n("force_ocr"), value=False, info=i18n("force_ocr_info"))
is_ocr = gr.Checkbox(
label=i18n("force_ocr"),
value=False,
visible=is_force_ocr_option_visible(preferred_option),
info=i18n("force_ocr_info"),
)
# 添加事件处理
_private_api_kwargs = (
@@ -1953,34 +1981,14 @@ def main(ctx,
backend.change(
fn=update_interface,
inputs=[backend, hybrid_effort],
outputs=[ocr_options, formula_enable, backend, hybrid_effort],
**_private_api_kwargs
)
# 服务器地址区域单独更新,避免 Gradio load 多输出影响首次切换到 http-client 的隐藏 Row 挂载。
backend.change(
fn=update_client_options_visibility,
inputs=backend,
outputs=client_options,
**_private_api_kwargs
)
# 图片分析显隐单独更新,避免 Gradio load 多输出影响首次 medium -> high 的隐藏组件挂载。
backend.change(
fn=update_image_analysis_visibility,
inputs=[backend, hybrid_effort],
outputs=image_analysis,
outputs=[ocr_language_options, is_ocr, formula_enable, backend, hybrid_effort],
**_private_api_kwargs
)
# 添加demo.load事件,在页面加载时触发一次界面更新
demo.load(
fn=update_interface,
inputs=[backend, hybrid_effort],
outputs=[ocr_options, formula_enable, backend, hybrid_effort],
**_private_api_kwargs
)
hybrid_effort.change(
fn=update_image_analysis_visibility,
inputs=[backend, hybrid_effort],
outputs=image_analysis,
outputs=[ocr_language_options, is_ocr, formula_enable, backend, hybrid_effort],
**_private_api_kwargs
)
clear_bu.add([input_file, md, doc_show, md_text, content_list_json, output_file, is_ocr, office_html, status_panel])
+10
View File
@@ -169,6 +169,16 @@ body.mineru-advanced-popover-open .mineru-advanced-popover {
visibility: hidden !important;
pointer-events: none !important;
}
.mineru-client-options,
.mineru-image-analysis-option {
display: none !important;
}
body.mineru-show-client-options .gradio-container .contain .mineru-client-options.mineru-client-options {
display: flex !important;
}
body.mineru-show-image-analysis .gradio-container .contain .mineru-image-analysis-option.mineru-image-analysis-option {
display: block !important;
}
.mineru-advanced-popover ul.options {
border-radius: 8px !important;
box-shadow: var(--mineru-popover-dropdown-shadow) !important;
+50
View File
@@ -6,6 +6,8 @@
window.__mineruAdvancedPopoverInstalled = POPOVER_SCRIPT_VERSION;
const POPOVER_OPEN_CLASS = "mineru-advanced-popover-open";
const CLIENT_OPTIONS_VISIBLE_CLASS = "mineru-show-client-options";
const IMAGE_ANALYSIS_VISIBLE_CLASS = "mineru-show-image-analysis";
const OFFICE_PREVIEW_NOTICE_STORAGE_KEY = "mineru.officePreviewNoticeIgnored";
const OPEN_DELAY_MS = 120;
const CLOSE_DELAY_MS = 280;
@@ -91,6 +93,7 @@
const refreshMineruCustomHtml = () => {
localizeMineruCustomText();
applyOfficePreviewNoticePreference();
refreshMineruOptionVisibility();
};
// 兼容 Gradio 将 elem_classes 挂到按钮自身或按钮外层容器的两种 DOM 结构。
@@ -98,10 +101,51 @@
"button.mineru-advanced-open, .mineru-advanced-open button, .mineru-advanced-open"
);
const findPopover = () => document.querySelector(".mineru-advanced-popover");
const findBackendRoot = () => document.querySelector(".mineru-backend-select");
const findEffortRoot = () => document.querySelector(".mineru-hybrid-effort");
let openTimer = null;
let closeTimer = null;
let visibilityTimer = null;
let hoverHandlersInstalled = false;
// 读取 Gradio Dropdown 当前值;value 属性比可见文本更稳定,避免中英文文案影响判断。
const getBackendValue = () => {
const backendRoot = findBackendRoot();
const backendControl = backendRoot?.querySelector('[role="listbox"]');
return (backendControl?.value || backendControl?.textContent || "").trim();
};
// 读取 Hybrid effort 当前值;控件在非 hybrid 后端会被 Gradio 隐藏,缺失时按空值处理。
const getEffortValue = () => {
const effortRoot = findEffortRoot();
const checkedRadio = effortRoot?.querySelector(
'input[type="radio"]:checked, input[type="radio"][aria-checked="true"]'
);
return (checkedRadio?.value || "").trim();
};
// 根据当前 backend/effort 刷新前端状态类,避免依赖 Gradio 重新挂载隐藏组件。
const refreshMineruOptionVisibility = () => {
const backend = getBackendValue();
const effort = getEffortValue();
const showClientOptions = backend.endsWith("http-client");
const showImageAnalysis = backend.startsWith("vlm")
|| (backend.startsWith("hybrid") && effort === "high");
document.body.classList.toggle(CLIENT_OPTIONS_VISIBLE_CLASS, showClientOptions);
document.body.classList.toggle(IMAGE_ANALYSIS_VISIBLE_CLASS, showImageAnalysis);
if (document.body.classList.contains(POPOVER_OPEN_CLASS)) {
positionPopover();
}
};
// Gradio 控件会异步写回 value,延后一帧再读可以覆盖 Dropdown option 点击和 Radio 切换。
const queueMineruOptionVisibilityRefresh = () => {
requestAnimationFrame(() => {
refreshMineruOptionVisibility();
requestAnimationFrame(refreshMineruOptionVisibility);
});
};
const findUploadFileInput = () => {
const uploadRoot = document.querySelector(".mineru-upload-file");
if (!uploadRoot) {
@@ -450,6 +494,7 @@
if (!(target instanceof Element)) {
return;
}
queueMineruOptionVisibilityRefresh();
if (target.closest(".office-preview-ignore-forever")) {
const notice = target.closest(".office-preview-notice");
if (setOfficePreviewNoticeIgnored()) {
@@ -488,11 +533,16 @@
document.addEventListener("input", (event) => {
const target = event.target;
queueMineruOptionVisibilityRefresh();
if (target instanceof Element && target.closest(".mineru-advanced-popover")) {
queueDropdownPosition();
}
});
document.addEventListener("change", () => {
queueMineruOptionVisibilityRefresh();
});
document.addEventListener("keydown", (event) => {
if (event.key === "Escape") {
closePopover();