import os import re from typing import Callable import gradio as gr import modelscope_gradio_components as mgr from .parse_markdown import parse_markdown with open(os.path.join(os.path.dirname(__file__), "tab-link.js")) as f: tab_link_js = f.read() custom_components = { "tab-link": { "props": ["tab", "component-tab"], "js": tab_link_js } } def remove_formatter(markdown_text): pattern = r"^---[\s\S]*?---" replaced_text = re.sub(pattern, "", markdown_text) return replaced_text def list_demos(dir_path: str, prefix=''): result = [] if (not os.path.isdir(dir_path)): return result for name in os.listdir(dir_path): path = os.path.join(dir_path, name) if os.path.isfile(path): result.append(prefix + name) elif os.path.isdir(path): sub_prefix = prefix + name + '/' result.extend(list_demos(path, sub_prefix)) return result def get_demo_modules(file_path: str): import importlib.util demos = [ demo for demo in list_demos( os.path.join(os.path.dirname(file_path), "demos")) if demo.endswith(".py") ] demo_modules = {} for demo in demos: demo_name = demo.split(".")[0] spec = importlib.util.spec_from_file_location( "demo", os.path.join(os.path.dirname(file_path), "demos", demo)) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) demo_modules[demo_name] = module return demo_modules class Docs: def __init__(self, file_path: str, markdown_files: list = None): self.file_path = file_path self.demo_modules = get_demo_modules(file_path) # default current directory self.markdown_files = markdown_files if markdown_files else [ file_name for file_name in os.listdir(os.path.dirname(file_path)) if file_name.endswith(".md") ] self.tabs = None def read_file(self, relative_path: str): with open(os.path.join(os.path.dirname(self.file_path), relative_path), "r") as f: return f.read() def render_demo(self, demo_name, prefix='', suffix=''): content = self.read_file(f"./demos/{demo_name}.py") module = self.demo_modules[demo_name] with gr.Accordion("Show Demo", open=False): with gr.Row(): with gr.Column(): mgr.Markdown(f""" {prefix} ````python {content} ```` {suffix} """, header_links=True, custom_components=custom_components) with gr.Column(): module.demo.render() def render_markdown(self, markdown_file, on_tab_link_click: Callable = None, components_tabs=None): items = parse_markdown(remove_formatter(self.read_file(markdown_file)), read_file=self.read_file) for item in items: if item["type"] == "text": md = mgr.Markdown(item["value"], header_links=True, custom_components=custom_components) # 过滤 deps = [dep for dep in [components_tabs, self.tabs] if dep] if len(deps) > 0: md.custom(fn=on_tab_link_click, outputs=deps) elif item["type"] == "demo": self.render_demo(item["name"], prefix=item["prefix"], suffix=item["suffix"]) def render(self, components_tabs=None): def tab_link_click(data: gr.EventData): tab: str = data._data["value"].get("tab", '') component_tab: str = data._data["value"].get("component_tab", '') if tab and tabs: return {tabs: gr.update(selected=tab)} elif components_tabs and component_tab: return {components_tabs: gr.update(selected=component_tab)} with gr.Blocks() as demo: if len(self.markdown_files) > 1: with gr.Tabs() as tabs: self.tabs = tabs for markdown_file in self.markdown_files: tab_name = ".".join(markdown_file.split(".")[:-1]) with gr.TabItem(tab_name, id=tab_name): self.render_markdown( markdown_file, on_tab_link_click=tab_link_click, components_tabs=components_tabs) else: self.render_markdown(self.markdown_files[0], on_tab_link_click=tab_link_click, components_tabs=components_tabs) return demo