From 459c5b2d24be332ea105f24a4e737d3a757ae2f3 Mon Sep 17 00:00:00 2001 From: binary-husky Date: Sat, 18 May 2024 20:23:50 +0800 Subject: [PATCH] plugin refactor: phase 1 --- crazy_functional.py | 7 +- .../{PDF批量翻译.py => PDF_Translate.py} | 0 crazy_functions/PDF_Translate_Wrap.py | 21 ++++ .../plugin_template/plugin_class_template.py | 57 ++++++++++ docs/self_analysis.md | 10 +- docs/translate_english.json | 2 +- docs/translate_japanese.json | 2 +- docs/translate_std.json | 2 +- docs/translate_traditionalchinese.json | 2 +- main.py | 101 +++++------------- tests/test_plugins.py | 4 +- themes/common.js | 99 ++++++++++++++++- themes/gui_advanced_plugin_class.py | 39 +++++++ themes/gui_floating_menu.py | 41 +++++++ themes/gui_toolbar.py | 34 ++++++ toolbox.py | 9 +- version | 4 +- 17 files changed, 338 insertions(+), 96 deletions(-) rename crazy_functions/{PDF批量翻译.py => PDF_Translate.py} (100%) create mode 100644 crazy_functions/PDF_Translate_Wrap.py create mode 100644 crazy_functions/plugin_template/plugin_class_template.py create mode 100644 themes/gui_advanced_plugin_class.py create mode 100644 themes/gui_floating_menu.py create mode 100644 themes/gui_toolbar.py diff --git a/crazy_functional.py b/crazy_functional.py index 80160ad9..a48b96ba 100644 --- a/crazy_functional.py +++ b/crazy_functional.py @@ -27,7 +27,7 @@ def get_crazy_functions(): from crazy_functions.辅助功能 import 清除缓存 from crazy_functions.批量Markdown翻译 import Markdown英译中 from crazy_functions.批量总结PDF文档 import 批量总结PDF文档 - from crazy_functions.PDF批量翻译 import 批量翻译PDF文档 + from crazy_functions.PDF_Translate import 批量翻译PDF文档 from crazy_functions.谷歌检索小助手 import 谷歌检索小助手 from crazy_functions.理解PDF文档内容 import 理解PDF文档内容标准文件输入 from crazy_functions.Latex全文润色 import Latex中文润色 @@ -36,6 +36,8 @@ def get_crazy_functions(): from crazy_functions.虚空终端 import 虚空终端 from crazy_functions.生成多种Mermaid图表 import 生成多种Mermaid图表 + from crazy_functions.PDF_Translate_Wrap import PDF_Tran + function_plugins = { "虚空终端": { "Group": "对话|编程|学术|智能体", @@ -209,7 +211,8 @@ def get_crazy_functions(): "Color": "stop", "AsButton": True, "Info": "精准翻译PDF论文为中文 | 输入参数为路径", - "Function": HotReload(批量翻译PDF文档), + "Function": None, + "Class": PDF_Tran, # 新一代插件都会写成 class }, "询问多个GPT模型": { "Group": "对话", diff --git a/crazy_functions/PDF批量翻译.py b/crazy_functions/PDF_Translate.py similarity index 100% rename from crazy_functions/PDF批量翻译.py rename to crazy_functions/PDF_Translate.py diff --git a/crazy_functions/PDF_Translate_Wrap.py b/crazy_functions/PDF_Translate_Wrap.py new file mode 100644 index 00000000..c3984896 --- /dev/null +++ b/crazy_functions/PDF_Translate_Wrap.py @@ -0,0 +1,21 @@ +from .PDF_Translate import 批量翻译PDF文档 +from crazy_functions.plugin_template.plugin_class_template import GptAcademicPluginTemplate, ArgProperty + +class PDF_Tran(GptAcademicPluginTemplate): + def __init__(self): + pass + + def define_arg_selection_menu(self): + gui_definition = { + "main_input": + ArgProperty(title="PDF文件路径", description="上传文件后,会自动生成路径", default_value="", type="string").model_dump_json(), # 主输入,自动从输入框同步 + "advanced_arg": + ArgProperty(title="高级参数输入区", description="无", default_value="", type="string").model_dump_json(), # 高级参数输入区,自动同步 + "additional_01": + ArgProperty(title="附属参数", description="无", default_value="没有附属参数", type="string").model_dump_json(), # 高级参数输入区,自动同步 + } + return gui_definition + + def execute(txt, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, user_request): + print(txt, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, user_request) + yield from 批量翻译PDF文档(txt, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, user_request) \ No newline at end of file diff --git a/crazy_functions/plugin_template/plugin_class_template.py b/crazy_functions/plugin_template/plugin_class_template.py new file mode 100644 index 00000000..e9a40814 --- /dev/null +++ b/crazy_functions/plugin_template/plugin_class_template.py @@ -0,0 +1,57 @@ +import os, json, base64 +from pydantic import BaseModel, Field +from textwrap import dedent + +class ArgProperty(BaseModel): + title: str = Field(description="The title", default="") + description: str = Field(description="The description", default="") + default_value: str|float = Field(description="The default value", default="") + type: str = Field(description="The type", default="") + +class GptAcademicPluginTemplate(): + def __init__(self): + # please note that `execute` method may run in different threads, + # thus you should not store any state in the plugin instance, + # which may be accessed by multiple threads + pass + + def define_arg_selection_menu(self): + """ + An example as below: + ``` + def define_arg_selection_menu(self): + gui_definition = { + "main_input": + ArgProperty(title="main input", description="description", default_value="default_value", type="string").model_dump_json(), + "advanced_arg": + ArgProperty(title="advanced arguments", description="description", default_value="default_value", type="string").model_dump_json(), + "additional_arg_01": + ArgProperty(title="additional", description="description", default_value="default_value", type="string").model_dump_json(), + } + return gui_definition + ``` + """ + raise NotImplementedError("You need to implement this method in your plugin class") + + + def get_js_code_for_generating_menu(self, btnName): + define_arg_selection = self.define_arg_selection_menu() + + if len(define_arg_selection.keys()) > 8: + raise ValueError("You can only have up to 8 arguments in the define_arg_selection") + if "main_input" not in define_arg_selection: + raise ValueError("You must have a 'main_input' in the define_arg_selection") + if "advanced_arg" not in define_arg_selection: + raise ValueError("You must have a 'main_input' in the define_arg_selection") + + DEFINE_ARG_INPUT_INTERFACE = json.dumps(define_arg_selection) + return dedent(""" + ()=>generate_menu("{GUI_JS}", "{BTN_NAME}") + """.format( + GUI_JS=base64.b64encode(DEFINE_ARG_INPUT_INTERFACE.encode('utf-8')).decode('utf-8'), + BTN_NAME=btnName + ) + ) + + def execute(txt, llm_kwargs, plugin_kwargs, chatbot, history, system_prompt, user_request): + raise NotImplementedError("You need to implement this method in your plugin class") \ No newline at end of file diff --git a/docs/self_analysis.md b/docs/self_analysis.md index e4fee61a..d63179c4 100644 --- a/docs/self_analysis.md +++ b/docs/self_analysis.md @@ -28,7 +28,7 @@ | crazy_functions\批量Markdown翻译.py | 将指定目录下的Markdown文件进行中英文翻译 | | crazy_functions\批量总结PDF文档.py | 对PDF文件进行切割和摘要生成 | | crazy_functions\批量总结PDF文档pdfminer.py | 对PDF文件进行文本内容的提取和摘要生成 | -| crazy_functions\PDF批量翻译.py | 将指定目录下的PDF文件进行中英文翻译 | +| crazy_functions\PDF_Translate.py | 将指定目录下的PDF文件进行中英文翻译 | | crazy_functions\理解PDF文档内容.py | 对PDF文件进行摘要生成和问题解答 | | crazy_functions\生成函数注释.py | 自动生成Python函数的注释 | | crazy_functions\联网的ChatGPT.py | 使用网络爬虫和ChatGPT模型进行聊天回答 | @@ -187,9 +187,9 @@ toolbox.py是一个工具类库,其中主要包含了一些函数装饰器和 该程序文件是一个用于批量总结PDF文档的函数插件,使用了pdfminer插件和BeautifulSoup库来提取PDF文档的文本内容,对每个PDF文件分别进行处理并生成中英文摘要。同时,该程序文件还包括一些辅助工具函数和处理异常的装饰器。 -## [24/48] 请对下面的程序文件做一个概述: crazy_functions\PDF批量翻译.py +## [24/48] 请对下面的程序文件做一个概述: crazy_functions\PDF_Translate.py -这个程序文件是一个Python脚本,文件名为“PDF批量翻译.py”。它主要使用了“toolbox”、“request_gpt_model_in_new_thread_with_ui_alive”、“request_gpt_model_multi_threads_with_very_awesome_ui_and_high_efficiency”、“colorful”等Python库和自定义的模块“crazy_utils”的一些函数。程序实现了一个批量翻译PDF文档的功能,可以自动解析PDF文件中的基础信息,递归地切割PDF文件,翻译和处理PDF论文中的所有内容,并生成相应的翻译结果文件(包括md文件和html文件)。功能比较复杂,其中需要调用多个函数和依赖库,涉及到多线程操作和UI更新。文件中有详细的注释和变量命名,代码比较清晰易读。 +这个程序文件是一个Python脚本,文件名为“PDF_Translate.py”。它主要使用了“toolbox”、“request_gpt_model_in_new_thread_with_ui_alive”、“request_gpt_model_multi_threads_with_very_awesome_ui_and_high_efficiency”、“colorful”等Python库和自定义的模块“crazy_utils”的一些函数。程序实现了一个批量翻译PDF文档的功能,可以自动解析PDF文件中的基础信息,递归地切割PDF文件,翻译和处理PDF论文中的所有内容,并生成相应的翻译结果文件(包括md文件和html文件)。功能比较复杂,其中需要调用多个函数和依赖库,涉及到多线程操作和UI更新。文件中有详细的注释和变量命名,代码比较清晰易读。 ## [25/48] 请对下面的程序文件做一个概述: crazy_functions\理解PDF文档内容.py @@ -331,7 +331,7 @@ check_proxy.py, colorful.py, config.py, config_private.py, core_functional.py, c 这些程序源文件提供了基础的文本和语言处理功能、工具函数和高级插件,使 Chatbot 能够处理各种复杂的学术文本问题,包括润色、翻译、搜索、下载、解析等。 ## 用一张Markdown表格简要描述以下文件的功能: -crazy_functions\代码重写为全英文_多线程.py, crazy_functions\图片生成.py, crazy_functions\对话历史存档.py, crazy_functions\总结word文档.py, crazy_functions\总结音视频.py, crazy_functions\批量Markdown翻译.py, crazy_functions\批量总结PDF文档.py, crazy_functions\批量总结PDF文档pdfminer.py, crazy_functions\PDF批量翻译.py, crazy_functions\理解PDF文档内容.py, crazy_functions\生成函数注释.py, crazy_functions\联网的ChatGPT.py, crazy_functions\解析JupyterNotebook.py, crazy_functions\解析项目源代码.py, crazy_functions\询问多个大语言模型.py, crazy_functions\读文章写摘要.py。根据以上分析,用一句话概括程序的整体功能。 +crazy_functions\代码重写为全英文_多线程.py, crazy_functions\图片生成.py, crazy_functions\对话历史存档.py, crazy_functions\总结word文档.py, crazy_functions\总结音视频.py, crazy_functions\批量Markdown翻译.py, crazy_functions\批量总结PDF文档.py, crazy_functions\批量总结PDF文档pdfminer.py, crazy_functions\PDF_Translate.py, crazy_functions\理解PDF文档内容.py, crazy_functions\生成函数注释.py, crazy_functions\联网的ChatGPT.py, crazy_functions\解析JupyterNotebook.py, crazy_functions\解析项目源代码.py, crazy_functions\询问多个大语言模型.py, crazy_functions\读文章写摘要.py。根据以上分析,用一句话概括程序的整体功能。 | 文件名 | 功能简述 | | --- | --- | @@ -343,7 +343,7 @@ crazy_functions\代码重写为全英文_多线程.py, crazy_functions\图片生 | 批量Markdown翻译.py | 将指定目录下的Markdown文件进行中英文翻译 | | 批量总结PDF文档.py | 对PDF文件进行切割和摘要生成 | | 批量总结PDF文档pdfminer.py | 对PDF文件进行文本内容的提取和摘要生成 | -| PDF批量翻译.py | 将指定目录下的PDF文件进行中英文翻译 | +| PDF_Translate.py | 将指定目录下的PDF文件进行中英文翻译 | | 理解PDF文档内容.py | 对PDF文件进行摘要生成和问题解答 | | 生成函数注释.py | 自动生成Python函数的注释 | | 联网的ChatGPT.py | 使用网络爬虫和ChatGPT模型进行聊天回答 | diff --git a/docs/translate_english.json b/docs/translate_english.json index d84bee8f..7669cff3 100644 --- a/docs/translate_english.json +++ b/docs/translate_english.json @@ -44,7 +44,7 @@ "批量总结PDF文档": "BatchSummarizePDFDocuments", "批量总结PDF文档pdfminer": "BatchSummarizePDFDocumentsUsingPdfminer", "批量翻译PDF文档": "BatchTranslatePDFDocuments", - "PDF批量翻译": "BatchTranslatePDFDocuments_MultiThreaded", + "PDF_Translate": "BatchTranslatePDFDocuments_MultiThreaded", "谷歌检索小助手": "GoogleSearchAssistant", "理解PDF文档内容标准文件输入": "UnderstandPdfDocumentContentStandardFileInput", "理解PDF文档内容": "UnderstandPdfDocumentContent", diff --git a/docs/translate_japanese.json b/docs/translate_japanese.json index 65bca570..c1df398e 100644 --- a/docs/translate_japanese.json +++ b/docs/translate_japanese.json @@ -44,7 +44,7 @@ "批量总结PDF文档": "BatchSummarizePDFDocuments", "批量总结PDF文档pdfminer": "BatchSummarizePDFDocumentsUsingPDFMiner", "批量翻译PDF文档": "BatchTranslatePDFDocuments", - "PDF批量翻译": "BatchTranslatePDFDocumentsUsingMultiThreading", + "PDF_Translate": "BatchTranslatePDFDocumentsUsingMultiThreading", "谷歌检索小助手": "GoogleSearchAssistant", "理解PDF文档内容标准文件输入": "StandardFileInputForUnderstandingPDFDocumentContent", "理解PDF文档内容": "UnderstandingPDFDocumentContent", diff --git a/docs/translate_std.json b/docs/translate_std.json index 3b5bf63e..a8b255db 100644 --- a/docs/translate_std.json +++ b/docs/translate_std.json @@ -6,7 +6,7 @@ "Latex英文纠错加PDF对比": "CorrectEnglishInLatexWithPDFComparison", "下载arxiv论文并翻译摘要": "DownloadArxivPaperAndTranslateAbstract", "Markdown翻译指定语言": "TranslateMarkdownToSpecifiedLanguage", - "PDF批量翻译": "BatchTranslatePDFDocuments_MultiThreaded", + "PDF_Translate": "BatchTranslatePDFDocuments_MultiThreaded", "下载arxiv论文翻译摘要": "DownloadArxivPaperTranslateAbstract", "解析一个Python项目": "ParsePythonProject", "解析一个Golang项目": "ParseGolangProject", diff --git a/docs/translate_traditionalchinese.json b/docs/translate_traditionalchinese.json index ee15d594..c4e42857 100644 --- a/docs/translate_traditionalchinese.json +++ b/docs/translate_traditionalchinese.json @@ -43,7 +43,7 @@ "批量总结PDF文档": "BatchSummarizePDFDocuments", "批量总结PDF文档pdfminer": "BatchSummarizePDFDocumentsPdfminer", "批量翻译PDF文档": "BatchTranslatePDFDocuments", - "PDF批量翻译": "BatchTranslatePdfDocumentsMultithreaded", + "PDF_Translate": "BatchTranslatePdfDocumentsMultithreaded", "谷歌检索小助手": "GoogleSearchAssistant", "理解PDF文档内容标准文件输入": "StandardFileInputForUnderstandingPdfDocumentContent", "理解PDF文档内容": "UnderstandingPdfDocumentContent", diff --git a/main.py b/main.py index 5e245ef5..b035008c 100644 --- a/main.py +++ b/main.py @@ -85,7 +85,6 @@ def main(): gr.HTML(title_html) secret_css = gr.Textbox(visible=False, elem_id="secret_css") - cookies, web_cookie_cache = make_cookie_cache() # 定义 后端state(cookies)、前端(web_cookie_cache)两兄弟 with gr_L1(): with gr_L2(scale=2, elem_id="gpt-chat"): @@ -144,7 +143,7 @@ def main(): with gr.Row(): dropdown = gr.Dropdown(dropdown_fn_list, value=r"点击这里搜索插件列表", label="", show_label=False).style(container=False) with gr.Row(): - plugin_advanced_arg = gr.Textbox(show_label=True, label="高级参数输入区", visible=False, + plugin_advanced_arg = gr.Textbox(show_label=True, label="高级参数输入区", visible=False, elem_id="advance_arg_input_legacy", placeholder="这里是特殊函数插件的高级参数输入区").style(container=False) with gr.Row(): switchy_bt = gr.Button(r"请先从插件列表中选择", variant="secondary").style(size="sm") @@ -152,76 +151,17 @@ def main(): with gr.Accordion("点击展开“文件下载区”。", open=False) as area_file_up: file_upload = gr.Files(label="任何文件, 推荐上传压缩文件(zip, tar)", file_count="multiple", elem_id="elem_upload") - with gr.Floating(init_x="0%", init_y="0%", visible=True, width=None, drag="forbidden", elem_id="tooltip"): - with gr.Row(): - with gr.Tab("上传文件", elem_id="interact-panel"): - gr.Markdown("请上传本地文件/压缩包供“函数插件区”功能调用。请注意: 上传文件后会自动把输入区修改为相应路径。") - file_upload_2 = gr.Files(label="任何文件, 推荐上传压缩文件(zip, tar)", file_count="multiple", elem_id="elem_upload_float") - - with gr.Tab("更换模型", elem_id="interact-panel"): - md_dropdown = gr.Dropdown(AVAIL_LLM_MODELS, value=LLM_MODEL, elem_id="elem_model_sel", label="更换LLM模型/请求源").style(container=False) - top_p = gr.Slider(minimum=-0, maximum=1.0, value=1.0, step=0.01,interactive=True, label="Top-p (nucleus sampling)",) - temperature = gr.Slider(minimum=-0, maximum=2.0, value=1.0, step=0.01, interactive=True, label="Temperature", elem_id="elem_temperature") - max_length_sl = gr.Slider(minimum=256, maximum=1024*32, value=4096, step=128, interactive=True, label="Local LLM MaxLength",) - system_prompt = gr.Textbox(show_label=True, lines=2, placeholder=f"System Prompt", label="System prompt", value=INIT_SYS_PROMPT, elem_id="elem_prompt") - temperature.change(None, inputs=[temperature], outputs=None, - _js="""(temperature)=>gpt_academic_gradio_saveload("save", "elem_prompt", "js_temperature_cookie", temperature)""") - system_prompt.change(None, inputs=[system_prompt], outputs=None, - _js="""(system_prompt)=>gpt_academic_gradio_saveload("save", "elem_prompt", "js_system_prompt_cookie", system_prompt)""") - md_dropdown.change(None, inputs=[md_dropdown], outputs=None, - _js="""(md_dropdown)=>gpt_academic_gradio_saveload("save", "elem_model_sel", "js_md_dropdown_cookie", md_dropdown)""") - - with gr.Tab("界面外观", elem_id="interact-panel"): - theme_dropdown = gr.Dropdown(AVAIL_THEMES, value=THEME, label="更换UI主题").style(container=False) - checkboxes = gr.CheckboxGroup(["基础功能区", "函数插件区", "浮动输入区", "输入清除键", "插件参数区"], value=["基础功能区", "函数插件区"], label="显示/隐藏功能区", elem_id='cbs').style(container=False) - opt = ["自定义菜单"] - value=[] - if ADD_WAIFU: opt += ["添加Live2D形象"]; value += ["添加Live2D形象"] - checkboxes_2 = gr.CheckboxGroup(opt, value=value, label="显示/隐藏自定义菜单", elem_id='cbsc').style(container=False) - dark_mode_btn = gr.Button("切换界面明暗 ☀", variant="secondary").style(size="sm") - dark_mode_btn.click(None, None, None, _js=js_code_for_toggle_darkmode) - with gr.Tab("帮助", elem_id="interact-panel"): - gr.Markdown(help_menu_description) - - with gr.Floating(init_x="20%", init_y="50%", visible=False, width="40%", drag="top") as area_input_secondary: - with gr.Accordion("浮动输入区", open=True, elem_id="input-panel2"): - with gr.Row() as row: - row.style(equal_height=True) - with gr.Column(scale=10): - txt2 = gr.Textbox(show_label=False, placeholder="Input question here.", - elem_id='user_input_float', lines=8, label="输入区2").style(container=False) - with gr.Column(scale=1, min_width=40): - submitBtn2 = gr.Button("提交", variant="primary"); submitBtn2.style(size="sm") - resetBtn2 = gr.Button("重置", variant="secondary"); resetBtn2.style(size="sm") - stopBtn2 = gr.Button("停止", variant="secondary"); stopBtn2.style(size="sm") - clearBtn2 = gr.Button("清除", elem_id="elem_clear2", variant="secondary", visible=False); clearBtn2.style(size="sm") - - - with gr.Floating(init_x="20%", init_y="50%", visible=False, width="40%", drag="top") as area_customize: - with gr.Accordion("自定义菜单", open=True, elem_id="edit-panel"): - with gr.Row() as row: - with gr.Column(scale=10): - AVAIL_BTN = [btn for btn in customize_btns.keys()] + [k for k in functional] - basic_btn_dropdown = gr.Dropdown(AVAIL_BTN, value="自定义按钮1", label="选择一个需要自定义基础功能区按钮").style(container=False) - basic_fn_title = gr.Textbox(show_label=False, placeholder="输入新按钮名称", lines=1).style(container=False) - basic_fn_prefix = gr.Textbox(show_label=False, placeholder="输入新提示前缀", lines=4).style(container=False) - basic_fn_suffix = gr.Textbox(show_label=False, placeholder="输入新提示后缀", lines=4).style(container=False) - with gr.Column(scale=1, min_width=70): - basic_fn_confirm = gr.Button("确认并保存", variant="primary"); basic_fn_confirm.style(size="sm") - basic_fn_clean = gr.Button("恢复默认", variant="primary"); basic_fn_clean.style(size="sm") - - from shared_utils.cookie_manager import assign_btn__fn_builder - assign_btn = assign_btn__fn_builder(customize_btns, predefined_btns, cookies, web_cookie_cache) - # update btn - h = basic_fn_confirm.click(assign_btn, [web_cookie_cache, cookies, basic_btn_dropdown, basic_fn_title, basic_fn_prefix, basic_fn_suffix], - [web_cookie_cache, cookies, *customize_btns.values(), *predefined_btns.values()]) - h.then(None, [web_cookie_cache], None, _js="""(web_cookie_cache)=>{setCookie("web_cookie_cache", web_cookie_cache, 365);}""") - # clean up btn - h2 = basic_fn_clean.click(assign_btn, [web_cookie_cache, cookies, basic_btn_dropdown, basic_fn_title, basic_fn_prefix, basic_fn_suffix, gr.State(True)], - [web_cookie_cache, cookies, *customize_btns.values(), *predefined_btns.values()]) - h2.then(None, [web_cookie_cache], None, _js="""(web_cookie_cache)=>{setCookie("web_cookie_cache", web_cookie_cache, 365);}""") + from themes.gui_toolbar import define_gui_toolbar + checkboxes, checkboxes_2, max_length_sl, theme_dropdown, system_prompt, file_upload_2, md_dropdown, top_p, temperature = \ + define_gui_toolbar(AVAIL_LLM_MODELS, LLM_MODEL, INIT_SYS_PROMPT, THEME, AVAIL_THEMES, ADD_WAIFU, help_menu_description, js_code_for_toggle_darkmode) + from themes.gui_floating_menu import define_gui_floating_menu + area_input_secondary, txt2, area_customize, submitBtn2, resetBtn2, clearBtn2, stopBtn2 = \ + define_gui_floating_menu(customize_btns, functional, predefined_btns, cookies, web_cookie_cache) + from themes.gui_advanced_plugin_class import define_gui_advanced_plugin_class + new_plugin_callback, route_switchy_bt_with_arg, usr_confirmed_arg = \ + define_gui_advanced_plugin_class(plugins) # 功能区显示开关与功能区的互动 def fn_area_visibility(a): @@ -244,6 +184,7 @@ def main(): # 整理反复出现的控件句柄组合 input_combo = [cookies, max_length_sl, md_dropdown, txt, txt2, top_p, temperature, chatbot, history, system_prompt, plugin_advanced_arg] + input_combo_order = ["cookies", "max_length_sl", "md_dropdown", "txt", "txt2", "top_p", "temperature", "chatbot", "history", "system_prompt", "plugin_advanced_arg"] output_combo = [cookies, chatbot, history, status] predict_args = dict(fn=ArgsGeneralWrapper(predict), inputs=[*input_combo, gr.State(True)], outputs=output_combo) # 提交按钮、重置按钮 @@ -253,8 +194,7 @@ def main(): cancel_handles.append(submitBtn2.click(**predict_args)) resetBtn.click(None, None, [chatbot, history, status], _js=js_code_reset) # 先在前端快速清除chatbot&status resetBtn2.click(None, None, [chatbot, history, status], _js=js_code_reset) # 先在前端快速清除chatbot&status - reset_server_side_args = (lambda history: ([], [], "已重置", json.dumps(history)), - [history], [chatbot, history, status, history_cache]) + reset_server_side_args = (lambda history: ([], [], "已重置", json.dumps(history)), [history], [chatbot, history, status, history_cache]) resetBtn.click(*reset_server_side_args) # 再在后端清除history,把history转存history_cache备用 resetBtn2.click(*reset_server_side_args) # 再在后端清除history,把history转存history_cache备用 clearBtn.click(None, None, [txt, txt2], _js=js_code_clear) @@ -278,9 +218,13 @@ def main(): # 函数插件-固定按钮区 for k in plugins: if not plugins[k].get("AsButton", True): continue - click_handle = plugins[k]["Button"].click(ArgsGeneralWrapper(plugins[k]["Function"]), [*input_combo], output_combo) - click_handle.then(on_report_generated, [cookies, file_upload, chatbot], [cookies, file_upload, chatbot]).then(None, [plugins[k]["Button"]], None, _js=r"(fn)=>on_plugin_exe_complete(fn)") - cancel_handles.append(click_handle) + if plugins[k].get("Function", None): + click_handle = plugins[k]["Button"].click(ArgsGeneralWrapper(plugins[k]["Function"]), [*input_combo], output_combo) + click_handle.then(on_report_generated, [cookies, file_upload, chatbot], [cookies, file_upload, chatbot]).then(None, [plugins[k]["Button"]], None, _js=r"(fn)=>on_plugin_exe_complete(fn)") + cancel_handles.append(click_handle) + elif "Class" in plugins[k]: + click_handle = plugins[k]["Button"].click(None, inputs=[], outputs=None, _js=plugins[k]["Class"]().get_js_code_for_generating_menu(k)) + # 函数插件-下拉菜单与随变按钮的互动 def on_dropdown_changed(k): variant = plugins[k]["Color"] if "Color" in plugins[k] else "secondary" @@ -319,6 +263,13 @@ def main(): click_handle = switchy_bt.click(route,[switchy_bt, *input_combo], output_combo) click_handle.then(on_report_generated, [cookies, file_upload, chatbot], [cookies, file_upload, chatbot]).then(None, [switchy_bt], None, _js=r"(fn)=>on_plugin_exe_complete(fn)") cancel_handles.append(click_handle) + + # 新一代插件的高级参数区确认按钮(隐藏) + click_handle = new_plugin_callback.click(route_switchy_bt_with_arg, [ + gr.State(["new_plugin_callback", "usr_confirmed_arg"] + input_combo_order), + new_plugin_callback, usr_confirmed_arg, *input_combo + ], output_combo) + # 终止按钮的回调函数注册 stopBtn.click(fn=None, inputs=None, outputs=None, cancels=cancel_handles) stopBtn2.click(fn=None, inputs=None, outputs=None, cancels=cancel_handles) diff --git a/tests/test_plugins.py b/tests/test_plugins.py index 527c29d8..cab42fd7 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -22,7 +22,7 @@ if __name__ == "__main__": # plugin_test(plugin='crazy_functions.Latex输出PDF->Latex翻译中文并重新编译PDF', main_input="2307.07522") - plugin_test(plugin='crazy_functions.PDF批量翻译->批量翻译PDF文档', main_input='build/pdf/t1.pdf') + plugin_test(plugin='crazy_functions.PDF_Translate->批量翻译PDF文档', main_input='build/pdf/t1.pdf') # plugin_test( # plugin="crazy_functions.Latex输出PDF->Latex翻译中文并重新编译PDF", @@ -45,7 +45,7 @@ if __name__ == "__main__": # plugin_test(plugin='crazy_functions.批量Markdown翻译->Markdown中译英', main_input="README.md") - # plugin_test(plugin='crazy_functions.PDF批量翻译->批量翻译PDF文档', main_input='crazy_functions/test_project/pdf_and_word/aaai.pdf') + # plugin_test(plugin='crazy_functions.PDF_Translate->批量翻译PDF文档', main_input='crazy_functions/test_project/pdf_and_word/aaai.pdf') # plugin_test(plugin='crazy_functions.谷歌检索小助手->谷歌检索小助手', main_input="https://scholar.google.com/scholar?hl=en&as_sdt=0%2C5&q=auto+reinforcement+learning&btnG=") diff --git a/themes/common.js b/themes/common.js index 8958af70..7fb622af 100644 --- a/themes/common.js +++ b/themes/common.js @@ -1525,8 +1525,103 @@ async function postData(url = '', data = {}) { } } - - +async function generate_menu(guiBase64String, btnName){ + // assign the button and menu data + push_data_to_gradio_component(guiBase64String, "invisible_current_pop_up_plugin_arg", "string"); + push_data_to_gradio_component(btnName, "invisible_callback_btn_for_plugin_exe", "string"); + + // Base64 to dict + const stringData = atob(guiBase64String); + let guiJsonData = JSON.parse(stringData); + let menu = document.getElementById("plugin_arg_menu"); + gui_args = {} + for (const key in guiJsonData) { + if (guiJsonData.hasOwnProperty(key)) { + const innerJSONString = guiJsonData[key]; + const decodedObject = JSON.parse(innerJSONString); + gui_args[key] = decodedObject; + } + } + + // 使参数菜单显现 + push_data_to_gradio_component({ + visible: true, + __type__: 'update' + }, "plugin_arg_menu", "obj"); + + // 根据 gui_args,使得对应参数项显现 + let text_cnt = 0; + for (const key in gui_args) { + if (gui_args.hasOwnProperty(key)) { + const component_name = "plugin_arg_txt_" + text_cnt; + if (gui_args[key].type=='string'){ + push_data_to_gradio_component({ + visible: true, + label: gui_args[key].title + "(" + gui_args[key].description + ")", + __type__: 'update' + }, component_name, "obj"); + if (key === "main_input"){ + // 为了与旧插件兼容,生成菜单时,自动加载输入栏的值 + let current_main_input = await get_data_from_gradio_component('user_input_main'); + let current_main_input_2 = await get_data_from_gradio_component('user_input_float'); + push_data_to_gradio_component(current_main_input + current_main_input_2, component_name, "obj"); + } + else if (key === "advanced_arg"){ + // 为了与旧插件兼容,生成菜单时,自动加载旧高级参数输入区的值 + let advance_arg_input_legacy = await get_data_from_gradio_component('advance_arg_input_legacy'); + push_data_to_gradio_component(advance_arg_input_legacy, component_name, "obj"); + } + else { + push_data_to_gradio_component(gui_args[key].default_value, component_name, "obj"); + } + text_cnt += 1; + } + } + } +} + +async function execute_current_pop_up_plugin(){ + let guiBase64String = await get_data_from_gradio_component('invisible_current_pop_up_plugin_arg'); + const stringData = atob(guiBase64String); + let guiJsonData = JSON.parse(stringData); + gui_args = {} + for (const key in guiJsonData) { + if (guiJsonData.hasOwnProperty(key)) { + const innerJSONString = guiJsonData[key]; + const decodedObject = JSON.parse(innerJSONString); + gui_args[key] = decodedObject; + } + } + // read user confirmed value + let text_cnt = 0; + for (const key in gui_args) { + if (gui_args.hasOwnProperty(key)) { + if (gui_args[key].type=='string'){ + corrisponding_elem_id = "plugin_arg_txt_"+text_cnt + gui_args[key].user_confirmed_value = await get_data_from_gradio_component(corrisponding_elem_id); + text_cnt += 1; + } + } + } + + // close menu + push_data_to_gradio_component({ + visible: false, + __type__: 'update' + }, "plugin_arg_menu", "obj"); + for (text_cnt = 0; text_cnt < 8; text_cnt++){ + push_data_to_gradio_component({ + visible: false, + label: "", + __type__: 'update' + }, "plugin_arg_txt_"+text_cnt, "obj"); + } + + // execute the plugin + push_data_to_gradio_component(JSON.stringify(gui_args), "invisible_current_pop_up_plugin_arg_final", "string"); + document.getElementById("invisible_callback_btn_for_plugin_exe").click(); + +} diff --git a/themes/gui_advanced_plugin_class.py b/themes/gui_advanced_plugin_class.py new file mode 100644 index 00000000..96244a03 --- /dev/null +++ b/themes/gui_advanced_plugin_class.py @@ -0,0 +1,39 @@ +import gradio as gr +import json +from toolbox import format_io, find_free_port, on_file_uploaded, on_report_generated, get_conf, ArgsGeneralWrapper, DummyWith + +def define_gui_advanced_plugin_class(plugins): + # 定义新一代插件的高级参数区 + with gr.Floating(init_x="40%", init_y="20%", visible=False, width="30%", drag="top", elem_id="plugin_arg_menu"): + with gr.Accordion("请选择并确认插件参数!", open=True, elem_id="plugin_arg_panel"): + for u in range(8): + with gr.Row(): + gr.Textbox(show_label=True, label="T1", placeholder="请输入", lines=1, visible=False, elem_id=f"plugin_arg_txt_{u}").style(container=False) + # for u in range(8): + # with gr.Row(): + # gr.Dropdown(label="T1", value="请选择", visible=False, elem_id=f"plugin_arg_drop_{u}").style(container=False) + with gr.Row(): + # 这个隐藏textbox负责装入当前弹出插件的属性 + gr.Textbox(show_label=False, placeholder="请输入", lines=1, visible=False, + elem_id=f"invisible_current_pop_up_plugin_arg").style(container=False) + usr_confirmed_arg = gr.Textbox(show_label=False, placeholder="请输入", lines=1, visible=False, + elem_id=f"invisible_current_pop_up_plugin_arg_final").style(container=False) + arg_confirm_btn = gr.Button("确认参数并执行", variant="primary"); + arg_confirm_btn.style(size="sm") + arg_confirm_btn.click(None, None, None, _js="""()=>execute_current_pop_up_plugin()""") + invisible_callback_btn_for_plugin_exe = gr.Button(r"未选定任何插件", variant="secondary", visible=False, elem_id="invisible_callback_btn_for_plugin_exe").style(size="sm") + # 随变按钮的回调函数注册 + def route_switchy_bt_with_arg(request: gr.Request, input_order, *arg): + arguments = {k:v for k,v in zip(input_order, arg)} + which_plugin = arguments.pop('new_plugin_callback') + if which_plugin in [r"未选定任何插件"]: return + usr_confirmed_arg = arguments.pop('usr_confirmed_arg') + arg_confirm: dict = {} + usr_confirmed_arg_dict = json.loads(usr_confirmed_arg) + for arg_name in usr_confirmed_arg_dict: + arg_confirm.update({arg_name: str(usr_confirmed_arg_dict[arg_name]['user_confirmed_value'])}) + plugin_obj = plugins[which_plugin]["Class"] + arguments['plugin_advanced_arg'] = arg_confirm + yield from ArgsGeneralWrapper(plugin_obj.execute)(request, *arguments.values()) + return invisible_callback_btn_for_plugin_exe, route_switchy_bt_with_arg, usr_confirmed_arg + diff --git a/themes/gui_floating_menu.py b/themes/gui_floating_menu.py new file mode 100644 index 00000000..003141f3 --- /dev/null +++ b/themes/gui_floating_menu.py @@ -0,0 +1,41 @@ +import gradio as gr + +def define_gui_floating_menu(customize_btns, functional, predefined_btns, cookies, web_cookie_cache): + with gr.Floating(init_x="20%", init_y="50%", visible=False, width="40%", drag="top") as area_input_secondary: + with gr.Accordion("浮动输入区", open=True, elem_id="input-panel2"): + with gr.Row() as row: + row.style(equal_height=True) + with gr.Column(scale=10): + txt2 = gr.Textbox(show_label=False, placeholder="Input question here.", + elem_id='user_input_float', lines=8, label="输入区2").style(container=False) + with gr.Column(scale=1, min_width=40): + submitBtn2 = gr.Button("提交", variant="primary"); submitBtn2.style(size="sm") + resetBtn2 = gr.Button("重置", variant="secondary"); resetBtn2.style(size="sm") + stopBtn2 = gr.Button("停止", variant="secondary"); stopBtn2.style(size="sm") + clearBtn2 = gr.Button("清除", elem_id="elem_clear2", variant="secondary", visible=False); clearBtn2.style(size="sm") + + + with gr.Floating(init_x="20%", init_y="50%", visible=False, width="40%", drag="top") as area_customize: + with gr.Accordion("自定义菜单", open=True, elem_id="edit-panel"): + with gr.Row() as row: + with gr.Column(scale=10): + AVAIL_BTN = [btn for btn in customize_btns.keys()] + [k for k in functional] + basic_btn_dropdown = gr.Dropdown(AVAIL_BTN, value="自定义按钮1", label="选择一个需要自定义基础功能区按钮").style(container=False) + basic_fn_title = gr.Textbox(show_label=False, placeholder="输入新按钮名称", lines=1).style(container=False) + basic_fn_prefix = gr.Textbox(show_label=False, placeholder="输入新提示前缀", lines=4).style(container=False) + basic_fn_suffix = gr.Textbox(show_label=False, placeholder="输入新提示后缀", lines=4).style(container=False) + with gr.Column(scale=1, min_width=70): + basic_fn_confirm = gr.Button("确认并保存", variant="primary"); basic_fn_confirm.style(size="sm") + basic_fn_clean = gr.Button("恢复默认", variant="primary"); basic_fn_clean.style(size="sm") + + from shared_utils.cookie_manager import assign_btn__fn_builder + assign_btn = assign_btn__fn_builder(customize_btns, predefined_btns, cookies, web_cookie_cache) + # update btn + h = basic_fn_confirm.click(assign_btn, [web_cookie_cache, cookies, basic_btn_dropdown, basic_fn_title, basic_fn_prefix, basic_fn_suffix], + [web_cookie_cache, cookies, *customize_btns.values(), *predefined_btns.values()]) + h.then(None, [web_cookie_cache], None, _js="""(web_cookie_cache)=>{setCookie("web_cookie_cache", web_cookie_cache, 365);}""") + # clean up btn + h2 = basic_fn_clean.click(assign_btn, [web_cookie_cache, cookies, basic_btn_dropdown, basic_fn_title, basic_fn_prefix, basic_fn_suffix, gr.State(True)], + [web_cookie_cache, cookies, *customize_btns.values(), *predefined_btns.values()]) + h2.then(None, [web_cookie_cache], None, _js="""(web_cookie_cache)=>{setCookie("web_cookie_cache", web_cookie_cache, 365);}""") + return area_input_secondary, txt2, area_customize, submitBtn2, resetBtn2, clearBtn2, stopBtn2 \ No newline at end of file diff --git a/themes/gui_toolbar.py b/themes/gui_toolbar.py new file mode 100644 index 00000000..ed026096 --- /dev/null +++ b/themes/gui_toolbar.py @@ -0,0 +1,34 @@ +import gradio as gr + +def define_gui_toolbar(AVAIL_LLM_MODELS, LLM_MODEL, INIT_SYS_PROMPT, THEME, AVAIL_THEMES, ADD_WAIFU, help_menu_description, js_code_for_toggle_darkmode): + with gr.Floating(init_x="0%", init_y="0%", visible=True, width=None, drag="forbidden", elem_id="tooltip"): + with gr.Row(): + with gr.Tab("上传文件", elem_id="interact-panel"): + gr.Markdown("请上传本地文件/压缩包供“函数插件区”功能调用。请注意: 上传文件后会自动把输入区修改为相应路径。") + file_upload_2 = gr.Files(label="任何文件, 推荐上传压缩文件(zip, tar)", file_count="multiple", elem_id="elem_upload_float") + + with gr.Tab("更换模型", elem_id="interact-panel"): + md_dropdown = gr.Dropdown(AVAIL_LLM_MODELS, value=LLM_MODEL, elem_id="elem_model_sel", label="更换LLM模型/请求源").style(container=False) + top_p = gr.Slider(minimum=-0, maximum=1.0, value=1.0, step=0.01,interactive=True, label="Top-p (nucleus sampling)",) + temperature = gr.Slider(minimum=-0, maximum=2.0, value=1.0, step=0.01, interactive=True, label="Temperature", elem_id="elem_temperature") + max_length_sl = gr.Slider(minimum=256, maximum=1024*32, value=4096, step=128, interactive=True, label="Local LLM MaxLength",) + system_prompt = gr.Textbox(show_label=True, lines=2, placeholder=f"System Prompt", label="System prompt", value=INIT_SYS_PROMPT, elem_id="elem_prompt") + temperature.change(None, inputs=[temperature], outputs=None, + _js="""(temperature)=>gpt_academic_gradio_saveload("save", "elem_prompt", "js_temperature_cookie", temperature)""") + system_prompt.change(None, inputs=[system_prompt], outputs=None, + _js="""(system_prompt)=>gpt_academic_gradio_saveload("save", "elem_prompt", "js_system_prompt_cookie", system_prompt)""") + md_dropdown.change(None, inputs=[md_dropdown], outputs=None, + _js="""(md_dropdown)=>gpt_academic_gradio_saveload("save", "elem_model_sel", "js_md_dropdown_cookie", md_dropdown)""") + + with gr.Tab("界面外观", elem_id="interact-panel"): + theme_dropdown = gr.Dropdown(AVAIL_THEMES, value=THEME, label="更换UI主题").style(container=False) + checkboxes = gr.CheckboxGroup(["基础功能区", "函数插件区", "浮动输入区", "输入清除键", "插件参数区"], value=["基础功能区", "函数插件区"], label="显示/隐藏功能区", elem_id='cbs').style(container=False) + opt = ["自定义菜单"] + value=[] + if ADD_WAIFU: opt += ["添加Live2D形象"]; value += ["添加Live2D形象"] + checkboxes_2 = gr.CheckboxGroup(opt, value=value, label="显示/隐藏自定义菜单", elem_id='cbsc').style(container=False) + dark_mode_btn = gr.Button("切换界面明暗 ☀", variant="secondary").style(size="sm") + dark_mode_btn.click(None, None, None, _js=js_code_for_toggle_darkmode) + with gr.Tab("帮助", elem_id="interact-panel"): + gr.Markdown(help_menu_description) + return checkboxes, checkboxes_2, max_length_sl, theme_dropdown, system_prompt, file_upload_2, md_dropdown, top_p, temperature \ No newline at end of file diff --git a/toolbox.py b/toolbox.py index e11efc12..63ee7dab 100644 --- a/toolbox.py +++ b/toolbox.py @@ -90,7 +90,7 @@ def ArgsGeneralWrapper(f): """ def decorated(request: gradio.Request, cookies:dict, max_length:int, llm_model:str, txt:str, txt2:str, top_p:float, temperature:float, chatbot:list, - history:list, system_prompt:str, plugin_advanced_arg:str, *args): + history:list, system_prompt:str, plugin_advanced_arg:str|dict, *args): txt_passon = txt if txt == "" and txt2 != "": txt_passon = txt2 # 引入一个有cookie的chatbot @@ -114,9 +114,10 @@ def ArgsGeneralWrapper(f): 'client_ip': request.client.host, 'most_recent_uploaded': cookies.get('most_recent_uploaded') } - plugin_kwargs = { - "advanced_arg": plugin_advanced_arg, - } + if isinstance(plugin_advanced_arg, str): + plugin_kwargs = {"advanced_arg": plugin_advanced_arg} + else: + plugin_kwargs = plugin_advanced_arg chatbot_with_cookie = ChatBotWithCookies(cookies) chatbot_with_cookie.write_list(chatbot) diff --git a/version b/version index a5b92ba9..197eb6d8 100644 --- a/version +++ b/version @@ -1,5 +1,5 @@ { - "version": 3.76, + "version": 3.80, "show_feature": true, - "new_feature": "上传文件时显示进度条 <-> 添加TTS语音输出(EdgeTTS和SoVits语音克隆) <-> Doc2x PDF翻译 <-> 添加回溯对话按钮" + "new_feature": "支持更复杂的插件框架 <-> 上传文件时显示进度条 <-> 添加TTS语音输出(EdgeTTS和SoVits语音克隆) <-> Doc2x PDF翻译 <-> 添加回溯对话按钮" }