622 lines
25 KiB
Python
622 lines
25 KiB
Python
# Copyright (c) Opendatalab. All rights reserved.
|
||
|
||
import base64
|
||
import os
|
||
import re
|
||
import sys
|
||
import time
|
||
import zipfile
|
||
from pathlib import Path
|
||
|
||
import click
|
||
import gradio as gr
|
||
from gradio_pdf import PDF
|
||
from loguru import logger
|
||
|
||
log_level = os.getenv("MINERU_LOG_LEVEL", "INFO").upper()
|
||
logger.remove() # 移除默认handler
|
||
logger.add(sys.stderr, level=log_level) # 添加新handler
|
||
|
||
from mineru.cli.common import prepare_env, read_fn, aio_do_parse, pdf_suffixes, image_suffixes
|
||
from mineru.utils.cli_parser import arg_parse
|
||
from mineru.utils.engine_utils import get_vlm_engine
|
||
from mineru.utils.hash_utils import str_sha256
|
||
|
||
|
||
# --- 新增:标准的树状思维导图生成函数 ---
|
||
def md_to_markmap_html(md_content):
|
||
"""
|
||
将 Markdown 转换为标准的树状思维导图 (Markmap)
|
||
"""
|
||
if not md_content:
|
||
return ""
|
||
|
||
# 转义 Markdown 中的反引号和符号,防止破坏 JS 字符串
|
||
safe_md = md_content.replace('`', '\\`').replace('$', '\\$')
|
||
|
||
# 完整的 HTML + Markmap 渲染引擎
|
||
full_html = f"""
|
||
<!DOCTYPE html>
|
||
<html>
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<style>
|
||
html, body, #mindmap {{ width: 100%; height: 100%; margin: 0; padding: 0; overflow: hidden; background-color: white; }}
|
||
</style>
|
||
<script src="https://cdn.jsdelivr.net/npm/d3@7"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/markmap-view"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/markmap-toolbar"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/markmap-lib"></script>
|
||
</head>
|
||
<body>
|
||
<svg id="mindmap"></svg>
|
||
<script>
|
||
// 等待页面完全加载后再初始化
|
||
document.addEventListener('DOMContentLoaded', function() {{
|
||
try {{
|
||
const {{ Markmap, loadCSS, loadJS, Transformer }} = window.markmap;
|
||
const transformer = new Transformer();
|
||
const {{ root, features }} = transformer.transform(`{safe_md}`);
|
||
const {{ styles, scripts }} = transformer.getAssets();
|
||
|
||
if (styles) loadCSS(styles);
|
||
if (scripts) loadJS(scripts);
|
||
|
||
// 增加延迟确保资源加载完成
|
||
setTimeout(() => {{
|
||
const mm = Markmap.create('#mindmap', {{
|
||
autoFit: true,
|
||
fitRatio: 0.9,
|
||
initialExpandLevel: -1
|
||
}}, root);
|
||
|
||
// 添加错误处理
|
||
if (mm) {{
|
||
console.log("Markmap created successfully");
|
||
}} else {{
|
||
console.error("Failed to create Markmap");
|
||
}}
|
||
}}, 500); // 延迟500毫秒以确保资源加载
|
||
}} catch (error) {{
|
||
console.error('Error initializing markmap:', error);
|
||
}}
|
||
}});
|
||
</script>
|
||
</body>
|
||
</html>
|
||
"""
|
||
|
||
# 使用 iframe 封装,彻底解决渲染失效问题
|
||
iframe_srcdoc = full_html.replace('"', '"')
|
||
iframe_code = f"""
|
||
<iframe srcdoc="{iframe_srcdoc}" style="width: 100%; height: 800px; border: 1px solid #ddd; border-radius: 8px;" sandbox="allow-scripts"></iframe>
|
||
"""
|
||
return iframe_code
|
||
|
||
|
||
# ────────────── 新增:根据上一级标题自动补全下一级标题 ──────────────
|
||
def auto_promote_paragraphs_to_subheading(text):
|
||
lines = text.splitlines()
|
||
result = []
|
||
in_section = False
|
||
empty_count = 0
|
||
|
||
for line in lines:
|
||
stripped = line.strip()
|
||
|
||
if stripped.startswith('# '):
|
||
result.append(line)
|
||
in_section = True
|
||
empty_count = 0
|
||
continue
|
||
|
||
if stripped.startswith('#'):
|
||
result.append(line)
|
||
in_section = False
|
||
empty_count = 0
|
||
continue
|
||
|
||
if not stripped:
|
||
result.append(line)
|
||
empty_count += 1
|
||
if empty_count >= 2:
|
||
in_section = False
|
||
continue
|
||
|
||
# 跳过图片、列表、代码等特殊行
|
||
if (
|
||
stripped.startswith('![') or
|
||
stripped.startswith('>') or
|
||
stripped.startswith('```') or
|
||
re.match(r'^[-*+] ', stripped) or
|
||
re.match(r'^\d+\. ', stripped)
|
||
):
|
||
result.append(line)
|
||
empty_count = 0
|
||
continue
|
||
|
||
empty_count = 0
|
||
if in_section:
|
||
result.append('## ' + stripped)
|
||
else:
|
||
result.append(line)
|
||
|
||
return '\n'.join(result)
|
||
|
||
|
||
async def parse_pdf(doc_path, output_dir, end_page_id, is_ocr, formula_enable, table_enable, language, backend, url):
|
||
os.makedirs(output_dir, exist_ok=True)
|
||
try:
|
||
file_name = f'{safe_stem(Path(doc_path).stem)}_{time.strftime("%y%m%d_%H%M%S")}'
|
||
pdf_data = read_fn(doc_path)
|
||
if backend.startswith("vlm"):
|
||
parse_method = "vlm"
|
||
else:
|
||
parse_method = 'ocr' if is_ocr else 'auto'
|
||
|
||
if backend.startswith("hybrid"):
|
||
env_name = f"hybrid_{parse_method}"
|
||
else:
|
||
env_name = parse_method
|
||
|
||
local_image_dir, local_md_dir = prepare_env(output_dir, file_name, env_name)
|
||
await aio_do_parse(
|
||
output_dir=output_dir,
|
||
pdf_file_names=[file_name],
|
||
pdf_bytes_list=[pdf_data],
|
||
p_lang_list=[language],
|
||
parse_method=parse_method,
|
||
end_page_id=end_page_id,
|
||
formula_enable=formula_enable,
|
||
table_enable=table_enable,
|
||
backend=backend,
|
||
server_url=url,
|
||
)
|
||
return local_md_dir, file_name
|
||
except Exception as e:
|
||
logger.exception(e)
|
||
return None
|
||
|
||
|
||
def compress_directory_to_zip(directory_path, output_zip_path):
|
||
try:
|
||
with zipfile.ZipFile(output_zip_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
|
||
for root, dirs, files in os.walk(directory_path):
|
||
for file in files:
|
||
file_path = os.path.join(root, file)
|
||
arcname = os.path.relpath(file_path, directory_path)
|
||
zipf.write(file_path, arcname)
|
||
return 0
|
||
except Exception as e:
|
||
logger.exception(e)
|
||
return -1
|
||
|
||
|
||
def image_to_base64(image_path):
|
||
with open(image_path, 'rb') as image_file:
|
||
return base64.b64encode(image_file.read()).decode('utf-8')
|
||
|
||
|
||
def replace_image_with_base64(markdown_text, image_dir_path):
|
||
pattern = r'\!\[(?:[^\]]*)\]\(([^)]+)\)'
|
||
|
||
def replace(match):
|
||
relative_path = match.group(1)
|
||
if relative_path.endswith('.jpg'):
|
||
full_path = os.path.join(image_dir_path, relative_path)
|
||
base64_image = image_to_base64(full_path)
|
||
return f''
|
||
return match.group(0)
|
||
|
||
return re.sub(pattern, replace, markdown_text)
|
||
|
||
|
||
async def to_markdown(file_path, end_pages=10, is_ocr=False, formula_enable=True, table_enable=True, language="ch",
|
||
backend="pipeline", url=None):
|
||
# 如果language包含(),则提取括号前的内容作为实际语言
|
||
if '(' in language and ')' in language:
|
||
language = language.split('(')[0].strip()
|
||
file_path = to_pdf(file_path)
|
||
# 打印请求参数日志
|
||
logger.info(f"parse_pdf 请求参数: file_path={file_path}, output_dir='./output', end_page_id={end_pages - 1}, "
|
||
f"is_ocr={is_ocr}, formula_enable={formula_enable}, table_enable={table_enable}, "
|
||
f"language='{language}', backend='{backend}', url={url}")
|
||
# 获取识别的md文件以及压缩包文件路径
|
||
local_md_dir, file_name = await parse_pdf(file_path, './output', end_pages - 1, is_ocr, formula_enable,
|
||
table_enable, language, backend, url)
|
||
archive_zip_path = os.path.join('./output', str_sha256(local_md_dir) + '.zip')
|
||
zip_archive_success = compress_directory_to_zip(local_md_dir, archive_zip_path)
|
||
if zip_archive_success == 0:
|
||
logger.info('Compression successful')
|
||
else:
|
||
logger.error('Compression failed')
|
||
md_path = os.path.join(local_md_dir, file_name + '.md')
|
||
with open(md_path, 'r', encoding='utf-8') as f:
|
||
txt_content = f.read()
|
||
|
||
# ────────────── 自动补全:根据 # 标题补全后续段落为 ## ──────────────
|
||
txt_content = auto_promote_paragraphs_to_subheading(txt_content)
|
||
# ────────────────────────────────────────────────────────────────
|
||
|
||
md_content = replace_image_with_base64(txt_content, local_md_dir)
|
||
|
||
# 生成思维导图HTML - 使用新的实现
|
||
mind_map_html = md_to_markmap_html(txt_content)
|
||
|
||
# 返回转换后的PDF路径
|
||
new_pdf_path = os.path.join(local_md_dir, file_name + '_layout.pdf')
|
||
|
||
return md_content, txt_content, archive_zip_path, new_pdf_path, mind_map_html
|
||
|
||
|
||
latex_delimiters_type_all = [
|
||
{'left': '$$', 'right': '$$', 'display': True},
|
||
{'left': '$', 'right': '$', 'display': False},
|
||
{'left': '\\(', 'right': '\\)', 'display': False},
|
||
{'left': '\\[', 'right': '\\]', 'display': True},
|
||
]
|
||
|
||
latex_delimiters_type_a = [
|
||
{'left': '$$', 'right': '$$', 'display': True},
|
||
{'left': '$', 'right': '$', 'display': False},
|
||
]
|
||
|
||
latex_delimiters_type_b = [
|
||
{'left': '\\(', 'right': '\\)', 'display': False},
|
||
{'left': '\\[', 'right': '\\]', 'display': True},
|
||
]
|
||
|
||
other_lang = ['ch (Chinese, English, Chinese Traditional)', 'en (English)', 'korean', 'japan']
|
||
all_lang = [*other_lang]
|
||
|
||
|
||
def safe_stem(file_path):
|
||
stem = Path(file_path).stem
|
||
return re.sub(r'[^\w.]', '_', stem)
|
||
|
||
|
||
def to_pdf(file_path):
|
||
if file_path is None: return None
|
||
pdf_bytes = read_fn(file_path)
|
||
unique_filename = f'{safe_stem(file_path)}.pdf'
|
||
tmp_file_path = os.path.join(os.path.dirname(file_path), unique_filename)
|
||
with open(tmp_file_path, 'wb') as tmp_pdf_file:
|
||
tmp_pdf_file.write(pdf_bytes)
|
||
return tmp_file_path
|
||
|
||
|
||
@click.command(context_settings=dict(ignore_unknown_options=True, allow_extra_args=True))
|
||
@click.pass_context
|
||
@click.option(
|
||
'--enable-example',
|
||
'example_enable',
|
||
type=bool,
|
||
help="Enable example files for input."
|
||
"The example files to be input need to be placed in the `example` folder within the directory where the command is currently executed.",
|
||
default=True,
|
||
)
|
||
@click.option(
|
||
'--enable-http-client',
|
||
'http_client_enable',
|
||
type=bool,
|
||
help="Enable http-client backend to link openai-compatible servers.",
|
||
default=False,
|
||
)
|
||
@click.option(
|
||
'--enable-api',
|
||
'api_enable',
|
||
type=bool,
|
||
help="Enable gradio API for serving the application.",
|
||
default=True,
|
||
)
|
||
@click.option(
|
||
'--max-convert-pages',
|
||
'max_convert_pages',
|
||
type=int,
|
||
help="Set the maximum number of pages to convert from PDF to Markdown.",
|
||
default=1000,
|
||
)
|
||
@click.option(
|
||
'--server-name',
|
||
'server_name',
|
||
type=str,
|
||
help="Set the server name for the Gradio app.",
|
||
default=None,
|
||
)
|
||
@click.option(
|
||
'--server-port',
|
||
'server_port',
|
||
type=int,
|
||
help="Set the server port for the Gradio app.",
|
||
default=None,
|
||
)
|
||
@click.option(
|
||
'--latex-delimiters-type',
|
||
'latex_delimiters_type',
|
||
type=click.Choice(['a', 'b', 'all']),
|
||
help="Set the type of LaTeX delimiters to use in Markdown rendering:"
|
||
"'a' for type '$', 'b' for type '()[]', 'all' for both types.",
|
||
default='all',
|
||
)
|
||
def main(ctx,
|
||
example_enable,
|
||
http_client_enable,
|
||
api_enable, max_convert_pages,
|
||
server_name, server_port, latex_delimiters_type, **kwargs
|
||
):
|
||
# 检测系统语言环境,默认为中文
|
||
import locale
|
||
import os
|
||
|
||
def detect_language():
|
||
# 检查环境变量
|
||
lang = os.getenv('LANG', '')
|
||
if 'zh' in lang.lower() or 'chinese' in lang.lower():
|
||
return 'zh'
|
||
|
||
# 检查系统默认locale
|
||
try:
|
||
default_locale = locale.getdefaultlocale()[0]
|
||
if default_locale and 'zh' in default_locale.lower():
|
||
return 'zh'
|
||
except:
|
||
pass
|
||
|
||
# 默认返回中文
|
||
return 'zh'
|
||
|
||
detected_lang = detect_language()
|
||
|
||
# 创建 i18n 实例,支持中英文,默认为中文
|
||
i18n = gr.I18n(
|
||
en={
|
||
"upload_file": "Please upload a PDF or image",
|
||
"max_pages": "Max convert pages",
|
||
"backend": "Backend",
|
||
"server_url": "Server URL",
|
||
"server_url_info": "OpenAI-compatible server URL for http-client backend.",
|
||
"recognition_options": "Recognition Options",
|
||
"table_enable": "Enable table recognition",
|
||
"table_info": "If disabled, tables will be shown as images.",
|
||
"formula_label_vlm": "Enable display formula recognition",
|
||
"formula_label_pipeline": "Enable formula recognition",
|
||
"formula_label_hybrid": "Enable inline formula recognition",
|
||
"formula_info_vlm": "If disabled, display formulas will be shown as images.",
|
||
"formula_info_pipeline": "If disabled, display formulas will be shown as images, and inline formulas will not be detected or parsed.",
|
||
"formula_info_hybrid": "If disabled, inline formulas will not be detected or parsed.",
|
||
"ocr_language": "OCR Language",
|
||
"ocr_language_info": "Select the OCR language for image-based PDFs and images.",
|
||
"force_ocr": "Force enable OCR",
|
||
"force_ocr_info": "Enable only if the result is extremely poor. Requires correct OCR language.",
|
||
"convert": "Convert",
|
||
"clear": "Clear",
|
||
"pdf_preview": "PDF preview",
|
||
"examples": "Examples:",
|
||
"convert_result": "Convert result",
|
||
"md_rendering": "Markdown rendering",
|
||
"md_text": "Markdown text",
|
||
"mind_map": "Mind Map", # 新增
|
||
"backend_info_vlm": "High-precision parsing via VLM, supports Chinese and English documents only.",
|
||
"backend_info_pipeline": "Traditional Multi-model pipeline parsing, supports multiple languages, hallucination-free.",
|
||
"backend_info_hybrid": "High-precision hybrid parsing, supports multiple languages.",
|
||
"backend_info_default": "Select the backend engine for document parsing.",
|
||
},
|
||
zh={
|
||
"upload_file": "请上传 PDF 或图片",
|
||
"max_pages": "最大转换页数",
|
||
"backend": "解析后端",
|
||
"server_url": "服务器地址",
|
||
"server_url_info": "http-client 后端的 OpenAI 兼容服务器地址。",
|
||
"recognition_options": "识别选项",
|
||
"table_enable": "启用表格识别",
|
||
"table_info": "禁用后,表格将显示为图片。",
|
||
"formula_label_vlm": "启用行间公式识别",
|
||
"formula_label_pipeline": "启用公式识别",
|
||
"formula_label_hybrid": "启用行内公式识别",
|
||
"formula_info_vlm": "禁用后,行间公式将显示为图片。",
|
||
"formula_info_pipeline": "禁用后,行间公式将显示为图片,行内公式将不会被检测或解析。",
|
||
"formula_info_hybrid": "禁用后,行内公式将不会被检测或解析。",
|
||
"ocr_language": "OCR 语言",
|
||
"ocr_language_info": "为扫描版 PDF 和图片选择 OCR 语言。",
|
||
"force_ocr": "强制启用 OCR",
|
||
"force_ocr_info": "仅在识别效果极差时启用,需选择正确的 OCR 语言。",
|
||
"convert": "转换",
|
||
"clear": "清除",
|
||
"pdf_preview": "PDF 预览",
|
||
"examples": "示例:",
|
||
"convert_result": "转换结果",
|
||
"md_rendering": "Markdown 渲染",
|
||
"md_text": "Markdown 文本",
|
||
"mind_map": "思维导图", # 新增
|
||
"backend_info_vlm": "多模态大模型高精度解析,仅支持中英文文档。",
|
||
"backend_info_pipeline": "传统多模型管道解析,支持多语言,无幻觉。",
|
||
"backend_info_hybrid": "高精度混合解析,支持多语言。",
|
||
"backend_info_default": "选择文档解析的后端引擎。",
|
||
},
|
||
)
|
||
|
||
# 根据后端类型获取公式识别标签(闭包函数以支持 i18n)
|
||
def get_formula_label(backend_choice):
|
||
if backend_choice.startswith("vlm"):
|
||
return i18n("formula_label_vlm")
|
||
elif backend_choice == "pipeline":
|
||
return i18n("formula_label_pipeline")
|
||
elif backend_choice.startswith("hybrid"):
|
||
return i18n("formula_label_hybrid")
|
||
else:
|
||
return i18n("formula_label_pipeline")
|
||
|
||
def get_formula_info(backend_choice):
|
||
if backend_choice.startswith("vlm"):
|
||
return i18n("formula_info_vlm")
|
||
elif backend_choice == "pipeline":
|
||
return i18n("formula_info_pipeline")
|
||
elif backend_choice.startswith("hybrid"):
|
||
return i18n("formula_info_hybrid")
|
||
else:
|
||
return ""
|
||
|
||
def get_backend_info(backend_choice):
|
||
if backend_choice.startswith("vlm"):
|
||
return i18n("backend_info_vlm")
|
||
elif backend_choice == "pipeline":
|
||
return i18n("backend_info_pipeline")
|
||
elif backend_choice.startswith("hybrid"):
|
||
return i18n("backend_info_hybrid")
|
||
else:
|
||
return i18n("backend_info_default")
|
||
|
||
# 更新界面函数
|
||
def update_interface(backend_choice):
|
||
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))
|
||
if "http-client" in backend_choice:
|
||
client_options_update = gr.update(visible=True)
|
||
else:
|
||
client_options_update = gr.update(visible=False)
|
||
if "vlm" in backend_choice:
|
||
ocr_options_update = gr.update(visible=False)
|
||
else:
|
||
ocr_options_update = gr.update(visible=True)
|
||
|
||
return client_options_update, ocr_options_update, formula_label_update, backend_info_update
|
||
|
||
kwargs.update(arg_parse(ctx))
|
||
|
||
if latex_delimiters_type == 'a':
|
||
latex_delimiters = latex_delimiters_type_a
|
||
elif latex_delimiters_type == 'b':
|
||
latex_delimiters = latex_delimiters_type_b
|
||
elif latex_delimiters_type == 'all':
|
||
latex_delimiters = latex_delimiters_type_all
|
||
else:
|
||
raise ValueError(f"Invalid latex delimiters type: {latex_delimiters_type}.")
|
||
|
||
vlm_engine = get_vlm_engine("auto", is_async=True)
|
||
if vlm_engine in ["transformers", "mlx-engine"]:
|
||
http_client_enable = True
|
||
else:
|
||
try:
|
||
logger.info(f"Start init {vlm_engine}...")
|
||
from mineru.backend.vlm.vlm_analyze import ModelSingleton
|
||
model_singleton = ModelSingleton()
|
||
predictor = model_singleton.get_model(
|
||
vlm_engine,
|
||
None,
|
||
None,
|
||
**kwargs
|
||
)
|
||
logger.info(f"{vlm_engine} init successfully.")
|
||
except Exception as e:
|
||
logger.exception(e)
|
||
|
||
suffixes = [f".{suffix}" for suffix in pdf_suffixes + image_suffixes]
|
||
with gr.Blocks(title="多模态思维导图助手",
|
||
fill_height=True) as demo:
|
||
# gr.HTML(header)
|
||
gr.HTML("<h1 style='text-align: center;'>多模态思维导图助手</h1>")
|
||
with gr.Row():
|
||
with gr.Column(variant='panel', scale=5):
|
||
with gr.Row():
|
||
input_file = gr.File(label=i18n("upload_file"), file_types=suffixes)
|
||
with gr.Row():
|
||
max_pages = gr.Slider(1, max_convert_pages, max_convert_pages, step=1, label=i18n("max_pages"))
|
||
with gr.Row():
|
||
drop_list = ["pipeline", "vlm-auto-engine", "hybrid-auto-engine"]
|
||
preferred_option = "hybrid-auto-engine"
|
||
if http_client_enable:
|
||
drop_list.extend(["vlm-http-client", "hybrid-http-client"])
|
||
backend = gr.Dropdown(drop_list, label=i18n("backend"), value=preferred_option,
|
||
info=get_backend_info(preferred_option))
|
||
with gr.Row(visible=False) as client_options:
|
||
url = gr.Textbox(label=i18n("server_url"), value='http://localhost:30000',
|
||
placeholder='http://localhost:30000', info=i18n("server_url_info"))
|
||
with gr.Row(equal_height=True):
|
||
with gr.Column():
|
||
gr.Markdown(i18n("recognition_options"))
|
||
table_enable = gr.Checkbox(label=i18n("table_enable"), value=True, info=i18n("table_info"))
|
||
formula_enable = gr.Checkbox(label=get_formula_label(preferred_option), value=True,
|
||
info=get_formula_info(preferred_option))
|
||
with gr.Column(visible=False) as ocr_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"))
|
||
with gr.Row():
|
||
change_bu = gr.Button(i18n("convert"))
|
||
clear_bu = gr.ClearButton(value=i18n("clear"))
|
||
pdf_show = PDF(label=i18n("pdf_preview"), interactive=False, visible=True, height=800)
|
||
if example_enable:
|
||
example_root = os.path.join(os.getcwd(), 'examples')
|
||
if os.path.exists(example_root):
|
||
gr.Examples(
|
||
label=i18n("examples"),
|
||
examples=[os.path.join(example_root, _) for _ in os.listdir(example_root) if
|
||
_.endswith(tuple(suffixes))],
|
||
inputs=input_file
|
||
)
|
||
|
||
with gr.Column(variant='panel', scale=5):
|
||
output_file = gr.File(label=i18n("convert_result"), interactive=False)
|
||
with gr.Tabs():
|
||
with gr.Tab(i18n("md_rendering")):
|
||
md = gr.Markdown(
|
||
label=i18n("md_rendering"),
|
||
height=1200,
|
||
show_copy_button=True,
|
||
latex_delimiters=latex_delimiters,
|
||
line_breaks=True
|
||
)
|
||
with gr.Tab(i18n("md_text")):
|
||
md_text = gr.TextArea(
|
||
lines=45,
|
||
show_copy_button=True,
|
||
label=i18n("md_text")
|
||
)
|
||
with gr.Tab(i18n("mind_map")): # 新增的思维导图tab
|
||
mind_map = gr.HTML(label=i18n("mind_map")) # 使用HTML组件来渲染markmap
|
||
|
||
# 添加事件处理
|
||
backend.change(
|
||
fn=update_interface,
|
||
inputs=[backend],
|
||
outputs=[client_options, ocr_options, formula_enable, backend],
|
||
api_name=False
|
||
)
|
||
# 添加demo.load事件,在页面加载时触发一次界面更新
|
||
demo.load(
|
||
fn=update_interface,
|
||
inputs=[backend],
|
||
outputs=[client_options, ocr_options, formula_enable, backend],
|
||
api_name=False
|
||
)
|
||
clear_bu.add([input_file, md, pdf_show, md_text, output_file, is_ocr, mind_map])
|
||
|
||
input_file.change(
|
||
fn=to_pdf,
|
||
inputs=input_file,
|
||
outputs=pdf_show,
|
||
api_name="to_pdf" if api_enable else False
|
||
)
|
||
change_bu.click(
|
||
fn=to_markdown,
|
||
inputs=[input_file, max_pages, is_ocr, formula_enable, table_enable, language, backend, url],
|
||
outputs=[md, md_text, output_file, pdf_show, mind_map], # 添加mind_map输出
|
||
api_name="to_markdown" if api_enable else False
|
||
)
|
||
|
||
footer_links = ["gradio", "settings"]
|
||
if api_enable:
|
||
footer_links.append("api")
|
||
demo.launch(
|
||
server_name=server_name,
|
||
server_port=server_port,
|
||
show_api=api_enable,
|
||
i18n=i18n,
|
||
# title = "多模态思维导图助手",
|
||
# favicon_path = "logo.png"
|
||
)
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main() |