diff --git a/main.py b/main.py index dd23d49b..e54f6d9a 100644 --- a/main.py +++ b/main.py @@ -34,7 +34,7 @@ def encode_plugin_info(k, plugin)->str: def main(): import gradio as gr - if gr.__version__ not in ['3.32.9', '3.32.10', '3.32.11']: + if gr.__version__ not in ['3.32.12']: raise ModuleNotFoundError("使用项目内置Gradio获取最优体验! 请运行 `pip install -r requirements.txt` 指令安装内置Gradio及其他依赖, 详情信息见requirements.txt.") # 一些基础工具 @@ -57,7 +57,7 @@ def main(): # 如果WEB_PORT是-1, 则随机选取WEB端口 PORT = find_free_port() if WEB_PORT <= 0 else WEB_PORT from check_proxy import get_current_version - from themes.theme import adjust_theme, advanced_css, theme_declaration, js_code_clear, js_code_reset, js_code_show_or_hide, js_code_show_or_hide_group2 + from themes.theme import adjust_theme, advanced_css, theme_declaration, js_code_clear, js_code_show_or_hide, js_code_show_or_hide_group2 from themes.theme import js_code_for_toggle_darkmode, js_code_for_persistent_cookie_init from themes.theme import load_dynamic_theme, to_cookie_str, from_cookie_str, assign_user_uuid title_html = f"

GPT 学术优化 {get_current_version()}

{theme_declaration}" @@ -106,7 +106,7 @@ def main(): with gr_L2(scale=2, elem_id="gpt-chat"): chatbot = gr.Chatbot(label=f"当前模型:{LLM_MODEL}", elem_id="gpt-chatbot") if LAYOUT == "TOP-DOWN": chatbot.style(height=CHATBOT_HEIGHT) - history, history_cache, history_cache_update = make_history_cache() # 定义 后端state(history)、前端(history_cache)、后端setter(history_cache_update)三兄弟 + history, _, _ = make_history_cache() # 定义 后端state(history)、前端(history_cache)、后端setter(history_cache_update)三兄弟 with gr_L2(scale=1, elem_id="gpt-panel"): with gr.Accordion("输入区", open=True, elem_id="input-panel") as area_input_primary: with gr.Row(): @@ -174,6 +174,7 @@ 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") + # 左上角工具栏定义 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 = \ @@ -183,6 +184,9 @@ def main(): from themes.gui_floating_menu import define_gui_floating_menu area_input_secondary, txt2, area_customize, _, resetBtn2, clearBtn2, stopBtn2 = \ define_gui_floating_menu(customize_btns, functional, predefined_btns, cookies, web_cookie_cache) + + # 浮动时间线定义 + gr.Spark(label="", value="") # 插件二级菜单的实现 from themes.gui_advanced_plugin_class import define_gui_advanced_plugin_class @@ -222,11 +226,11 @@ def main(): multiplex_sel.select( None, [multiplex_sel], None, _js=f"""(multiplex_sel)=>run_multiplex_shift(multiplex_sel)""") cancel_handles.append(submit_btn.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]) - resetBtn.click(*reset_server_side_args) # 再在后端清除history,把history转存history_cache备用 - resetBtn2.click(*reset_server_side_args) # 再在后端清除history,把history转存history_cache备用 + resetBtn.click(None, None, [chatbot, history, status], _js="""(a,b,c)=>clear_conversation(a,b,c)""") # 先在前端快速清除chatbot&status + resetBtn2.click(None, None, [chatbot, history, status], _js="""(a,b,c)=>clear_conversation(a,b,c)""") # 先在前端快速清除chatbot&status + # reset_server_side_args = (lambda history: ([], [], "已重置"), [history], [chatbot, history, status]) + # resetBtn.click(*reset_server_side_args) # 再在后端清除history + # resetBtn2.click(*reset_server_side_args) # 再在后端清除history clearBtn.click(None, None, [txt, txt2], _js=js_code_clear) clearBtn2.click(None, None, [txt, txt2], _js=js_code_clear) if AUTO_CLEAR_TXT: diff --git a/shared_utils/cookie_manager.py b/shared_utils/cookie_manager.py index 88fe3a91..d503ce77 100644 --- a/shared_utils/cookie_manager.py +++ b/shared_utils/cookie_manager.py @@ -77,16 +77,28 @@ def make_history_cache(): # 定义 后端state(history)、前端(history_cache)、后端setter(history_cache_update)三兄弟 import gradio as gr # 定义history的后端state - history = gr.State([]) - # 定义history的一个孪生的前端存储区(隐藏) - history_cache = gr.Textbox(visible=False, elem_id="history_cache") - # 定义history_cache->history的更新方法(隐藏)。在触发这个按钮时,会先执行js代码更新history_cache,然后再执行python代码更新history - def process_history_cache(history_cache): - return json.loads(history_cache) - # 另一种更简单的setter方法 - history_cache_update = gr.Button("", elem_id="elem_update_history", visible=False).click( - process_history_cache, inputs=[history_cache], outputs=[history]) - return history, history_cache, history_cache_update + # history = gr.State([]) + history = gr.Textbox(visible=False, elem_id="history-ng") + # # 定义history的一个孪生的前端存储区(隐藏) + # history_cache = gr.Textbox(visible=False, elem_id="history_cache") + # # 定义history_cache->history的更新方法(隐藏)。在触发这个按钮时,会先执行js代码更新history_cache,然后再执行python代码更新history + # def process_history_cache(history_cache): + # return json.loads(history_cache) + # # 另一种更简单的setter方法 + # history_cache_update = gr.Button("", elem_id="elem_update_history", visible=False).click( + # process_history_cache, inputs=[history_cache], outputs=[history]) + # # save history to history_cache + # def process_history_cache(history_cache): + # return json.dumps(history_cache) + # # 定义history->history_cache的更新方法(隐藏) + # def sync_history_cache(history): + # print("sync_history_cache", history) + # return json.dumps(history) + # # history.change(sync_history_cache, inputs=[history], outputs=[history_cache]) + + # # history_cache_sync = gr.Button("", elem_id="elem_sync_history", visible=False).click( + # # lambda history: (json.dumps(history)), inputs=[history_cache], outputs=[history]) + return history, None, None diff --git a/themes/common.js b/themes/common.js index c0b794cf..55727cc6 100644 --- a/themes/common.js +++ b/themes/common.js @@ -318,7 +318,7 @@ function addCopyButton(botElement, index, is_last_in_arr) { } }); - if (enable_tts){ + if (enable_tts) { var audioButton = document.createElement('button'); audioButton.classList.add('audio-toggle-btn'); audioButton.innerHTML = audioIcon; @@ -346,7 +346,7 @@ function addCopyButton(botElement, index, is_last_in_arr) { var messageBtnColumn = document.createElement('div'); messageBtnColumn.classList.add('message-btn-row'); messageBtnColumn.appendChild(copyButton); - if (enable_tts){ + if (enable_tts) { messageBtnColumn.appendChild(audioButton); } botElement.appendChild(messageBtnColumn); @@ -391,6 +391,8 @@ function chatbotContentChanged(attempt = 1, force = false) { // Now pass both the message element and the is_last_in_arr boolean to addCopyButton addCopyButton(message, index, is_last_in_arr); + + save_conversation_history(); }); // gradioApp().querySelectorAll('#gpt-chatbot .message-wrap .message.bot').forEach(addCopyButton); }, i === 0 ? 0 : 200); @@ -854,8 +856,7 @@ function limit_scroll_position() { // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= function loadLive2D() { - if (document.querySelector(".waifu") ) - { + if (document.querySelector(".waifu")) { $('.waifu').show(); } else { try { @@ -937,22 +938,140 @@ function gpt_academic_gradio_saveload( } } +function update_conversation_metadata() { + // Create a conversation UUID and timestamp + const conversationId = crypto.randomUUID(); + const timestamp = new Date().toISOString(); + const conversationData = { + id: conversationId, + timestamp: timestamp + }; + // Save to cookie + setCookie("conversation_metadata", JSON.stringify(conversationData), 2); + // read from cookie + let conversation_metadata = getCookie("conversation_metadata"); + console.log("conversation_metadata", conversation_metadata); +} + +// // Example schema for conversation data structure +// const example_conversation = { +// metadata: { +// id: "550e8400-e29b-41d4-a716-446655440000", +// timestamp: "2024-03-29T12:34:56.789Z" +// }, +// conversation: [ +// ["user", "Hello, how are you?"], +// ["assistant", "I'm doing well, thank you for asking! How can I help you today?"], +// ["user", "What is the weather like?"], +// ["assistant", "I don't have access to real-time weather information. You would need to check a weather service or look outside to know the current weather conditions."] +// ], +// preview: "A conversation about greetings and weather" +// } + +// Helper function to generate conversation preview +function generatePreview(conversation, maxLength = 100) { + if (!conversation || conversation.length === 0) return ""; + // Join all messages with dash separator + const preview = conversation.join("\n"); + if (preview.length <= maxLength) return preview; + return preview.substring(0, maxLength) + "..."; +} + +async function save_conversation_history() { + // 505030475 + let chatbot = await get_data_from_gradio_component('gpt-chatbot'); + let history = await get_data_from_gradio_component('history-ng'); + let conversation_metadata = getCookie("conversation_metadata"); + conversation_metadata = JSON.parse(conversation_metadata); + // console.log("conversation_metadata", conversation_metadata); + let conversation = { + timestamp: conversation_metadata.timestamp, + id: conversation_metadata.id, + metadata: conversation_metadata, + conversation: chatbot, + history: history, + preview: generatePreview(JSON.parse(history)) + }; + + // Get existing conversation history from local storage + let conversation_history = []; + try { + const stored = localStorage.getItem('conversation_history'); + if (stored) { + conversation_history = JSON.parse(stored); + } + } catch (e) { + // console.error('Error reading conversation history from localStorage:', e); + } + + // Find existing conversation with same ID + const existingIndex = conversation_history.findIndex(c => c.id === conversation.id); + + if (existingIndex >= 0) { + // Update existing conversation + conversation_history[existingIndex] = conversation; + } else { + // Add new conversation + conversation_history.push(conversation); + } + + // Sort conversations by timestamp, newest first + conversation_history.sort((a, b) => { + const timeA = new Date(a.timestamp).getTime(); + const timeB = new Date(b.timestamp).getTime(); + return timeB - timeA; + }); + + // Save back to local storage + try { + localStorage.setItem('conversation_history', JSON.stringify(conversation_history)); + const LOCAL_STORAGE_UPDATED = "gptac_conversation_history_updated"; + window.dispatchEvent( + new CustomEvent(LOCAL_STORAGE_UPDATED, { + detail: conversation_history + }) + ); + } catch (e) { + console.error('Error saving conversation history to localStorage:', e); + } +} + +function restore_chat_from_local_storage(event) { + let conversation = event.detail; + push_data_to_gradio_component(conversation.conversation, "gpt-chatbot", "obj"); + push_data_to_gradio_component(conversation.history, "history-ng", "obj"); + console.log("restore_chat_from_local_storage", conversation); +} + + +function clear_conversation(a, b, c) { + update_conversation_metadata(); + let stopButton = document.getElementById("elem_stop"); + stopButton.click(); + // console.log("clear_conversation"); + return reset_conversation(a, b); +} function reset_conversation(a, b) { // console.log("js_code_reset"); a = btoa(unescape(encodeURIComponent(JSON.stringify(a)))); setCookie("js_previous_chat_cookie", a, 1); + b = btoa(unescape(encodeURIComponent(JSON.stringify(b)))); + setCookie("js_previous_history_cookie", b, 1); gen_restore_btn(); return [[], [], "已重置"]; } // clear -> 将 history 缓存至 history_cache -> 点击复原 -> restore_previous_chat() -> 触发elem_update_history -> 读取 history_cache function restore_previous_chat() { - console.log("restore_previous_chat"); + // console.log("restore_previous_chat"); let chat = getCookie("js_previous_chat_cookie"); chat = JSON.parse(decodeURIComponent(escape(atob(chat)))); push_data_to_gradio_component(chat, "gpt-chatbot", "obj"); - document.querySelector("#elem_update_history").click(); // in order to call set_history_gr_state, and send history state to server + let history = getCookie("js_previous_history_cookie"); + history = JSON.parse(decodeURIComponent(escape(atob(history)))); + push_data_to_gradio_component(history, "history-ng", "obj"); + // document.querySelector("#elem_update_history").click(); // in order to call set_history_gr_state, and send history state to server } function gen_restore_btn() { @@ -1010,8 +1129,7 @@ function gen_restore_btn() { `; // only add when not exist - if (!document.getElementById('recvButtonStyle')) - { + if (!document.getElementById('recvButtonStyle')) { document.head.appendChild(styleSheet); } @@ -1033,8 +1151,7 @@ function gen_restore_btn() { document.body.removeChild(this); }); // only add when not exist - if (!document.getElementById('recvButton')) - { + if (!document.getElementById('recvButton')) { document.body.appendChild(button); } @@ -1043,7 +1160,7 @@ function gen_restore_btn() { } async function on_plugin_exe_complete(fn_name) { - console.log(fn_name); + // console.log(fn_name); if (fn_name === "保存当前的对话") { // get chat profile path let chatbot = await get_data_from_gradio_component('gpt-chatbot'); @@ -1070,7 +1187,7 @@ async function on_plugin_exe_complete(fn_name) { } -async function generate_menu(guiBase64String, btnName){ +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"); @@ -1104,22 +1221,22 @@ async function generate_menu(guiBase64String, btnName){ /////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////// Textbox //////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////// - if (gui_args[key].type=='string'){ // PLUGIN_ARG_MENU + if (gui_args[key].type == 'string') { // PLUGIN_ARG_MENU const component_name = "plugin_arg_txt_" + text_cnt; push_data_to_gradio_component({ visible: true, - label: gui_args[key].title + "(" + gui_args[key].description + ")", + label: gui_args[key].title + "(" + gui_args[key].description + ")", // label: gui_args[key].title, placeholder: gui_args[key].description, __type__: 'update' }, component_name, "obj"); - if (key === "main_input"){ + 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"){ + 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"); @@ -1134,12 +1251,12 @@ async function generate_menu(guiBase64String, btnName){ /////////////////////////////////////////////////////////////////////////////////////////// //////////////////////////////////// Dropdown //////////////////////////////////// /////////////////////////////////////////////////////////////////////////////////////////// - if (gui_args[key].type=='dropdown'){ // PLUGIN_ARG_MENU + if (gui_args[key].type == 'dropdown') { // PLUGIN_ARG_MENU const component_name = "plugin_arg_drop_" + dropdown_cnt; push_data_to_gradio_component({ visible: true, choices: gui_args[key].options, - label: gui_args[key].title + "(" + gui_args[key].description + ")", + label: gui_args[key].title + "(" + gui_args[key].description + ")", // label: gui_args[key].title, placeholder: gui_args[key].description, __type__: 'update' @@ -1154,7 +1271,7 @@ async function generate_menu(guiBase64String, btnName){ } } -async function execute_current_pop_up_plugin(){ +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); @@ -1170,8 +1287,8 @@ async function execute_current_pop_up_plugin(){ let text_cnt = 0; for (const key in gui_args) { if (gui_args.hasOwnProperty(key)) { - if (gui_args[key].type=='string'){ // PLUGIN_ARG_MENU - corrisponding_elem_id = "plugin_arg_txt_"+text_cnt + if (gui_args[key].type == 'string') { // PLUGIN_ARG_MENU + 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; } @@ -1180,8 +1297,8 @@ async function execute_current_pop_up_plugin(){ let dropdown_cnt = 0; for (const key in gui_args) { if (gui_args.hasOwnProperty(key)) { - if (gui_args[key].type=='dropdown'){ // PLUGIN_ARG_MENU - corrisponding_elem_id = "plugin_arg_drop_"+dropdown_cnt + if (gui_args[key].type == 'dropdown') { // PLUGIN_ARG_MENU + corrisponding_elem_id = "plugin_arg_drop_" + dropdown_cnt gui_args[key].user_confirmed_value = await get_data_from_gradio_component(corrisponding_elem_id); dropdown_cnt += 1; } @@ -1200,29 +1317,29 @@ async function execute_current_pop_up_plugin(){ } -function hide_all_elem(){ - // PLUGIN_ARG_MENU - for (text_cnt = 0; text_cnt < 8; text_cnt++){ +function hide_all_elem() { + // PLUGIN_ARG_MENU + 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"); - document.getElementById("plugin_arg_txt_"+text_cnt).parentNode.parentNode.style.display = 'none'; + }, "plugin_arg_txt_" + text_cnt, "obj"); + document.getElementById("plugin_arg_txt_" + text_cnt).parentNode.parentNode.style.display = 'none'; } - for (dropdown_cnt = 0; dropdown_cnt < 8; dropdown_cnt++){ + for (dropdown_cnt = 0; dropdown_cnt < 8; dropdown_cnt++) { push_data_to_gradio_component({ visible: false, choices: [], label: "", __type__: 'update' - }, "plugin_arg_drop_"+dropdown_cnt, "obj"); - document.getElementById("plugin_arg_drop_"+dropdown_cnt).parentNode.style.display = 'none'; + }, "plugin_arg_drop_" + dropdown_cnt, "obj"); + document.getElementById("plugin_arg_drop_" + dropdown_cnt).parentNode.style.display = 'none'; } } -function close_current_pop_up_plugin(){ - // PLUGIN_ARG_MENU +function close_current_pop_up_plugin() { + // PLUGIN_ARG_MENU push_data_to_gradio_component({ visible: false, __type__: 'update' @@ -1233,15 +1350,13 @@ function close_current_pop_up_plugin(){ // 生成高级插件的选择菜单 plugin_init_info_lib = {} -function register_plugin_init(key, base64String){ +function register_plugin_init(key, base64String) { // console.log('x') const stringData = atob(base64String); let guiJsonData = JSON.parse(stringData); - if (key in plugin_init_info_lib) - { + if (key in plugin_init_info_lib) { } - else - { + else { plugin_init_info_lib[key] = {}; } plugin_init_info_lib[key].info = guiJsonData.Info; @@ -1251,28 +1366,26 @@ function register_plugin_init(key, base64String){ plugin_init_info_lib[key].enable_advanced_arg = guiJsonData.AdvancedArgs; plugin_init_info_lib[key].arg_reminder = guiJsonData.ArgsReminder; } -function register_advanced_plugin_init_code(key, code){ - if (key in plugin_init_info_lib) - { +function register_advanced_plugin_init_code(key, code) { + if (key in plugin_init_info_lib) { } - else - { + else { plugin_init_info_lib[key] = {}; } plugin_init_info_lib[key].secondary_menu_code = code; } -function run_advanced_plugin_launch_code(key){ +function run_advanced_plugin_launch_code(key) { // convert js code string to function generate_menu(plugin_init_info_lib[key].secondary_menu_code, key); } -function on_flex_button_click(key){ - if (plugin_init_info_lib.hasOwnProperty(key) && plugin_init_info_lib[key].hasOwnProperty('secondary_menu_code')){ +function on_flex_button_click(key) { + if (plugin_init_info_lib.hasOwnProperty(key) && plugin_init_info_lib[key].hasOwnProperty('secondary_menu_code')) { run_advanced_plugin_launch_code(key); - }else{ + } else { document.getElementById("old_callback_btn_for_plugin_exe").click(); } } -async function run_dropdown_shift(dropdown){ +async function run_dropdown_shift(dropdown) { let key = dropdown; push_data_to_gradio_component({ value: key, @@ -1281,7 +1394,7 @@ async function run_dropdown_shift(dropdown){ __type__: 'update' }, "elem_switchy_bt", "obj"); - if (plugin_init_info_lib[key].enable_advanced_arg){ + if (plugin_init_info_lib[key].enable_advanced_arg) { push_data_to_gradio_component({ visible: true, label: plugin_init_info_lib[key].label, @@ -1303,9 +1416,9 @@ async function duplicate_in_new_window() { window.open(url, '_blank'); } -async function run_classic_plugin_via_id(plugin_elem_id){ - for (key in plugin_init_info_lib){ - if (plugin_init_info_lib[key].elem_id == plugin_elem_id){ +async function run_classic_plugin_via_id(plugin_elem_id) { + for (key in plugin_init_info_lib) { + if (plugin_init_info_lib[key].elem_id == plugin_elem_id) { // 获取按钮名称 let current_btn_name = await get_data_from_gradio_component(plugin_elem_id); // 执行 @@ -1326,7 +1439,7 @@ async function call_plugin_via_name(current_btn_name) { hide_all_elem(); // 为了与旧插件兼容,生成菜单时,自动加载旧高级参数输入区的值 let advance_arg_input_legacy = await get_data_from_gradio_component('advance_arg_input_legacy'); - if (advance_arg_input_legacy.length != 0){ + if (advance_arg_input_legacy.length != 0) { gui_args["advanced_arg"] = {}; gui_args["advanced_arg"].user_confirmed_value = advance_arg_input_legacy; } @@ -1353,7 +1466,7 @@ async function multiplex_function_begin(multiplex_sel) { // do not delete `REPLACE_EXTENDED_MULTIPLEX_FUNCTIONS_HERE`! It will be read and replaced by Python code. // REPLACE_EXTENDED_MULTIPLEX_FUNCTIONS_HERE } -async function run_multiplex_shift(multiplex_sel){ +async function run_multiplex_shift(multiplex_sel) { let key = multiplex_sel; if (multiplex_sel === "常规对话") { key = "提交"; diff --git a/themes/gui_floating_menu.py b/themes/gui_floating_menu.py index 90947c5d..614d9d97 100644 --- a/themes/gui_floating_menu.py +++ b/themes/gui_floating_menu.py @@ -1,7 +1,7 @@ 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.Floating(init_x="20%", init_y="50%", visible=False, width="40%", drag="top", elem_id="f_area_input_secondary") as area_input_secondary: with gr.Accordion("浮动输入区", open=True, elem_id="input-panel2"): with gr.Row() as row: row.style(equal_height=True) @@ -17,7 +17,7 @@ def define_gui_floating_menu(customize_btns, functional, predefined_btns, cookie 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.Floating(init_x="20%", init_y="50%", visible=False, width="40%", drag="top", elem_id="f_area_customize") as area_customize: with gr.Accordion("自定义菜单", open=True, elem_id="edit-panel"): with gr.Row() as row: with gr.Column(scale=10): diff --git a/themes/init.js b/themes/init.js index 9ea8dd5a..179223de 100644 --- a/themes/init.js +++ b/themes/init.js @@ -3,6 +3,8 @@ async function GptAcademicJavaScriptInit(dark, prompt, live2d, layout, tts) { audio_fn_init(); minor_ui_adjustment(); ButtonWithDropdown_init(); + update_conversation_metadata(); + window.addEventListener("gptac_restore_chat_from_local_storage", restore_chat_from_local_storage); // 加载欢迎页面 const welcomeMessage = new WelcomeMessage(); diff --git a/themes/theme.py b/themes/theme.py index 28195774..96c3eed6 100644 --- a/themes/theme.py +++ b/themes/theme.py @@ -92,15 +92,6 @@ js_code_for_persistent_cookie_init = """(web_cookie_cache, cookie) => { } """ -# 详见 themes/common.js -js_code_reset = """ -(a,b,c)=>{ - let stopButton = document.getElementById("elem_stop"); - stopButton.click(); - return reset_conversation(a,b); -} -""" - js_code_clear = """ (a,b)=>{ diff --git a/themes/welcome.js b/themes/welcome.js index 8296b1f5..a6378e5a 100644 --- a/themes/welcome.js +++ b/themes/welcome.js @@ -84,7 +84,7 @@ class WelcomeMessage { this.max_welcome_card_num = 6; this.card_array = []; this.static_welcome_message_previous = []; - this.reflesh_time_interval = 15*1000; + this.reflesh_time_interval = 15 * 1000; const reflesh_render_status = () => { @@ -105,7 +105,7 @@ class WelcomeMessage { async startRefleshCards() { await new Promise(r => setTimeout(r, this.reflesh_time_interval)); await this.reflesh_cards(); - if (this.visible){ + if (this.visible) { setTimeout(() => { this.startRefleshCards.call(this); }, 1); @@ -113,7 +113,7 @@ class WelcomeMessage { } async reflesh_cards() { - if (!this.visible){ + if (!this.visible) { return; } @@ -173,18 +173,18 @@ class WelcomeMessage { } shuffle(array) { - var currentIndex = array.length, randomIndex; + var currentIndex = array.length, randomIndex; // While there remain elements to shuffle... while (currentIndex != 0) { - // Pick a remaining element... - randomIndex = Math.floor(Math.random() * currentIndex); - currentIndex--; + // Pick a remaining element... + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex--; - // And swap it with the current element. - [array[currentIndex], array[randomIndex]] = [ - array[randomIndex], array[currentIndex]]; + // And swap it with the current element. + [array[currentIndex], array[randomIndex]] = [ + array[randomIndex], array[currentIndex]]; } return array; @@ -193,7 +193,7 @@ class WelcomeMessage { async update() { // console.log('update') var page_width = document.documentElement.clientWidth; - const width_to_hide_welcome = 1200; + const width_to_hide_welcome = 1000; if (!await this.isChatbotEmpty() || page_width < width_to_hide_welcome) { if (this.visible) { this.removeWelcome(); @@ -203,7 +203,7 @@ class WelcomeMessage { } return; } - if (this.visible){ + if (this.visible) { return; } // console.log("welcome"); @@ -220,28 +220,28 @@ class WelcomeMessage { const title = document.createElement('div'); title.classList.add('welcome-card-title'); - // 创建图标 - const svg = document.createElement('img'); - svg.classList.add('welcome-svg'); - svg.src = message.svg; - svg.style.height = '30px'; - title.appendChild(svg); + // 创建图标 + const svg = document.createElement('img'); + svg.classList.add('welcome-svg'); + svg.src = message.svg; + svg.style.height = '30px'; + title.appendChild(svg); - // 创建标题 - const text = document.createElement('a'); - text.textContent = message.title; - text.classList.add('welcome-title-text'); - text.href = message.url; - text.target = "_blank"; - title.appendChild(text) + // 创建标题 + const text = document.createElement('a'); + text.textContent = message.title; + text.classList.add('welcome-title-text'); + text.href = message.url; + text.target = "_blank"; + title.appendChild(text) // 创建内容 const content = document.createElement('div'); content.classList.add('welcome-content'); - const content_c = document.createElement('div'); - content_c.classList.add('welcome-content-c'); - content_c.textContent = message.content; - content.appendChild(content_c); + const content_c = document.createElement('div'); + content_c.classList.add('welcome-content-c'); + content_c.textContent = message.content; + content.appendChild(content_c); // 将标题和内容添加到卡片 div 中 card.appendChild(title); @@ -307,28 +307,28 @@ class WelcomeMessage { class PageFocusHandler { constructor() { - this.hasReturned = false; - this.focusCallbacks = []; + this.hasReturned = false; + this.focusCallbacks = []; - // Bind the focus and blur event handlers - window.addEventListener('visibilitychange', this.handleFocus.bind(this)); + // Bind the focus and blur event handlers + window.addEventListener('visibilitychange', this.handleFocus.bind(this)); } // Method to handle the focus event handleFocus() { - if (this.hasReturned) { - this.focusCallbacks.forEach(callback => callback()); - } - this.hasReturned = true; + if (this.hasReturned) { + this.focusCallbacks.forEach(callback => callback()); + } + this.hasReturned = true; } // Method to add a custom callback function addFocusCallback(callback) { - if (typeof callback === 'function') { - this.focusCallbacks.push(callback); - } else { - throw new Error('Callback must be a function'); - } + if (typeof callback === 'function') { + this.focusCallbacks.push(callback); + } else { + throw new Error('Callback must be a function'); + } } } diff --git a/toolbox.py b/toolbox.py index 060bf8dc..41c75178 100644 --- a/toolbox.py +++ b/toolbox.py @@ -8,6 +8,7 @@ import base64 import gradio import shutil import glob +import json import uuid from loguru import logger from functools import wraps @@ -92,8 +93,9 @@ 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:dict, *args): + json_history:str, system_prompt:str, plugin_advanced_arg:dict, *args): txt_passon = txt + history = json.loads(json_history) if json_history else [] if txt == "" and txt2 != "": txt_passon = txt2 # 引入一个有cookie的chatbot if request.username is not None: @@ -148,10 +150,11 @@ def ArgsGeneralWrapper(f): return decorated -def update_ui(chatbot:ChatBotWithCookies, history, msg="正常", **kwargs): # 刷新界面 +def update_ui(chatbot:ChatBotWithCookies, history:list, msg:str="正常", **kwargs): # 刷新界面 """ 刷新用户界面 """ + assert isinstance(history, list), "history必须是一个list" assert isinstance( chatbot, ChatBotWithCookies ), "在传递chatbot的过程中不要将其丢弃。必要时, 可用clear将其清空, 然后用for+append循环重新赋值。" @@ -175,10 +178,11 @@ def update_ui(chatbot:ChatBotWithCookies, history, msg="正常", **kwargs): # else: chatbot_gr = chatbot - yield cookies, chatbot_gr, history, msg + json_history = json.dumps(history, ensure_ascii=False) + yield cookies, chatbot_gr, json_history, msg -def update_ui_lastest_msg(lastmsg:str, chatbot:ChatBotWithCookies, history:list, delay=1, msg="正常"): # 刷新界面 +def update_ui_lastest_msg(lastmsg:str, chatbot:ChatBotWithCookies, history:list, delay:float=1, msg:str="正常"): # 刷新界面 """ 刷新用户界面 """