From 1107eba42b359fc7e6f79bf5e77819b122c8864b Mon Sep 17 00:00:00 2001 From: Conner Harkness Date: Tue, 3 Jun 2025 19:24:56 -0600 Subject: [PATCH] Completed per-conversation setting overrides --- app.py | 37 +++++++++- lib/gui.py | 3 + views/chats_default.py | 162 ++++++++++++++++++++++++----------------- views/more_about.py | 6 +- views/more_settings.py | 81 ++++++++++++++------- 5 files changed, 190 insertions(+), 99 deletions(-) diff --git a/app.py b/app.py index 561a9cc..ba135f5 100644 --- a/app.py +++ b/app.py @@ -67,7 +67,7 @@ if ss.TOKEN is not None: break ss.CHATS_DIR = f"{ss.CONFIG_DIR}/chats" -ss.SETTINGS = JsonFile(f"{ss.CONFIG_DIR}/settings.json", defaults={ +ss.SETTINGS = JsonFile(f"{ss.CONFIG_DIR}/settings.json", defaults=({ "fetch_reply": True, "save_as": False, "show_clear": False, @@ -76,6 +76,14 @@ ss.SETTINGS = JsonFile(f"{ss.CONFIG_DIR}/settings.json", defaults={ "show_more": True, "show_fetch_button": True, "show_fetch_toggle": True +} if ss.CONFIG_DIR != "." else {})) + +ss.IS_ADMIN = False +if ss.TOKEN == ss.TOKEN_LIST[0]["token"]: + ss.IS_ADMIN = True + +ss.APP_SETTINGS = JsonFile("settings.json", defaults={ + "inference_server_url": "http://127.0.0.1:11434/" }) # @@ -131,12 +139,10 @@ def register_pages(category, arr, fn, **kwargs): # # -if ss.TOKEN is None: - st.warning("A valid API token is required to use this software.") - from views.chats_default import * from views.more_about import * from views.more_settings import * +from views.more_tokens import * # # @@ -186,10 +192,33 @@ if ss.TOKEN is not None: register_page("More", "About", more_about, icon=":material/info:") register_page("More", "Settings", more_settings, icon=":material/settings:") +if ss.IS_ADMIN: + register_page("More", "Tokens", more_tokens, icon=":material/key:") + # # # +if ss.TOKEN is None: + st.sidebar.warning("A valid API token is required to use this software.") + + def save_token(): + set_cookie("token", ss.new_token) + page_redirect() + + token = st.sidebar.text_input( + "Token", + value=get_cookie("token"), + help="Provide a valid token here", + on_change=save_token, + key="new_token") + +else: + if len(ss.TOKEN_LIST) > 1: + if st.sidebar.button("Logout", icon=":material/logout:", use_container_width=True): + set_cookie("token", None) + page_redirect() + st.sidebar.caption("mllm-lite by caharkness") # Only attempt to handle redirect after all page objects exist: diff --git a/lib/gui.py b/lib/gui.py index c3b4e5d..1b3a22e 100644 --- a/lib/gui.py +++ b/lib/gui.py @@ -67,4 +67,7 @@ window.resize(1280, 640) window.setCentralWidget(webview) window.show() +# Allow browser to control the window's title: +webview.titleChanged.connect(window.setWindowTitle) + sys.exit(app.exec()) diff --git a/views/chats_default.py b/views/chats_default.py index a2278b7..224e32c 100644 --- a/views/chats_default.py +++ b/views/chats_default.py @@ -23,6 +23,7 @@ import streamlit as st ss = st.session_state def chats_default(item): + chat = {} chat_path = item["path"] chat_name = item["title"] @@ -40,31 +41,22 @@ def chats_default(item): "keep": 0, "pinned": False, "auto_clear": False, - #"custom_settings": False, - #"auto_fetch": ss.SETTINGS.get("auto_fetch"), - #"show_clear": ss.SETTINGS.get("show_clear"), - #"show_undo": ss.SETTINGS.get("show_undo"), - #"show_redo": ss.SETTINGS.get("show_redo"), - #"show_fetch_button": ss.SETTINGS.get("show_fetch_button"), - #"show_fetch_toggle": ss.SETTINGS.get("show_fetch_toggle"), + "hide_clear": False, + "hide_undo": False, + "hide_redo": False, + "hide_fetch_button": False, + "hide_fetch_toggle": False } + # + # + # + def load_defaults(): for k in chat_defaults.keys(): if k not in chat.keys(): chat[k] = chat_defaults[k] - load_defaults() - - #if not chat["custom_settings"]: - # del chat["auto_fetch"] - # del chat["show_clear"] - # del chat["show_undo"] - # del chat["show_redo"] - # del chat["show_fetch_button"] - # del chat["show_fetch_toggle"] - # load_defaults() - def save_chat(name=chat_name, overwrite=True): path = f"{ss.CHATS_DIR}/{name}.json" path = get_next_filename(path) if not overwrite else path @@ -86,6 +78,8 @@ def chats_default(item): # # + load_defaults() + st.caption("This is the beginning of the conversation") for message in chat["messages"]: @@ -102,7 +96,7 @@ def chats_default(item): with st.chat_message("assistant"): def stream_reply(input_data): response = requests.post( - f"http://127.0.0.1:11434/", + ss.APP_SETTINGS.get("inference_server_url"), data=input_data.encode("utf-8"), headers={"Content-Type": "text/plain"}, stream=True) @@ -137,7 +131,7 @@ def chats_default(item): save_chat() - if ss.SETTINGS.get("auto_fetch"): + if ss.SETTINGS.get("fetch_reply"): st.session_state.run = 1 st.rerun() @@ -167,55 +161,85 @@ def chats_default(item): st.session_state.run = 1 def button_more(): - @st.dialog("More") + @st.dialog(chat_name) def button_more_modal(): - tab_labels = ["General", "More"] + tab_labels = ["General", "Advanced", "Interface"] tabs = st.tabs(tab_labels) + save_button_group = None + action_button_group = None + if (t := "General") in tab_labels: with tabs[tab_labels.index(t)]: original_name = chat_name new_name = st.text_input("Name", value=chat_name) - new_context = st.text_area("Context", value=chat["context"]) + new_context = st.text_area("Context", value=chat["context"]) - if (t := "More") in tab_labels: + save_button_group = st.container() + + if (t := "Advanced") in tab_labels: with tabs[tab_labels.index(t)]: new_keep = st.number_input("Keep Messages", value=chat["keep"], help="Number of messages to keep from the top after a clear") with st.container(border=True): - save_as = st.toggle("Save as copy", value=ss.SETTINGS.get("save_as")) new_auto_clear = st.toggle("Auto clear", value=chat["auto_clear"]) new_pinned = st.toggle("Pinned", value=chat["pinned"]) - cols = st.columns([1, 1, 1, 1]) + action_button_group = st.container() - with cols[0]: - if st.button("Save", icon=":material/save:", use_container_width=True): - chat["context"] = new_context - chat["keep"] = new_keep - chat["pinned"] = new_pinned - chat["auto_clear"] = new_auto_clear + if (t := "Interface") in tab_labels: + with tabs[tab_labels.index(t)]: + new_hide_clear = st.toggle("Hide clear", value=chat["hide_clear"]) + new_hide_undo = st.toggle("Hide undo", value=chat["hide_undo"]) + new_hide_redo = st.toggle("Hide redo", value=chat["hide_redo"]) + new_hide_fetch_button = st.toggle("Hide fetch button", value=chat["hide_fetch_button"]) + new_hide_fetch_toggle = st.toggle("Hide fetch toggle", value=chat["hide_fetch_toggle"]) - goto_name = save_chat(name=new_name, overwrite=(not save_as)) + with action_button_group: + cols = st.columns([1, 1, 1]) + + with cols[0]: + if st.button("Clear", icon=":material/mop:", use_container_width=True): + chat["keep"] = new_keep + clear_chat() + save_chat() + redirect("Chats", original_name) + + with cols[1]: + if st.button("Delete", icon=":material/delete:", use_container_width=True): + os.unlink(chat_path) + st.rerun() + + with save_button_group: + cols = st.columns([1, 1, 1]) + + def save_common(): + chat["context"] = new_context + chat["keep"] = new_keep + chat["pinned"] = new_pinned + chat["auto_clear"] = new_auto_clear + chat["hide_clear"] = new_hide_clear + chat["hide_undo"] = new_hide_undo + chat["hide_redo"] = new_hide_redo + chat["hide_fetch_button"] = new_hide_fetch_button + chat["hide_fetch_toggle"] = new_hide_fetch_toggle + + with cols[0]: + if st.button("Save", icon=":material/save:", use_container_width=True): + save_common() + goto_name = save_chat(name=new_name, overwrite=True) - if save_as == False: if chat_name != new_name: os.unlink(chat_path) - redirect("Chats", goto_name) + redirect("Chats", goto_name) - with cols[1]: - if st.button("Clear", icon=":material/mop:", use_container_width=True): - chat["keep"] = new_keep - clear_chat() - save_chat() - redirect("Chats", original_name) - - with cols[2]: - if st.button("Delete", icon=":material/delete:", use_container_width=True): - os.unlink(chat_path) - st.rerun() + with cols[1]: + if st.button("Copy", icon=":material/file_copy:", use_container_width=True): + save_common() + goto_name = save_chat(name=new_name, overwrite=False) + redirect("Chats", goto_name) button_more_modal() @@ -225,30 +249,36 @@ def chats_default(item): cols = st.columns(7) cols_pos = -1 - if ss.SETTINGS.get("show_clear"): - if len(chat["messages"]) > abs(chat["keep"]): - with cols[(cols_pos := cols_pos + 1)]: - st.button("", icon=":material/mop:", on_click=button_clear, use_container_width=True) - if ss.SETTINGS.get("show_undo"): - if len(chat["messages"]) > 0: - with cols[(cols_pos := cols_pos + 1)]: - st.button("", icon=":material/undo:", on_click=button_undo, use_container_width=True) - - if ss.SETTINGS.get("show_redo"): - if len(chat["messages"]) > 1: - if chat["messages"][-1]["author"] == "assistant": + if not chat["hide_clear"]: + if ss.SETTINGS.get("show_clear"): + if len(chat["messages"]) > abs(chat["keep"]): with cols[(cols_pos := cols_pos + 1)]: - st.button("", icon=":material/redo:", on_click=button_redo, use_container_width=True) + st.button("", icon=":material/mop:", on_click=button_clear, use_container_width=True) - with cols[(cols_pos := cols_pos + 1)]: - st.button("", icon=":material/more_horiz:", on_click=button_more, use_container_width=True) + if not chat["hide_undo"]: + if ss.SETTINGS.get("show_undo"): + if len(chat["messages"]) > 0: + with cols[(cols_pos := cols_pos + 1)]: + st.button("", icon=":material/undo:", on_click=button_undo, use_container_width=True) - if ss.SETTINGS.get("show_fetch_button"): - if not ss.SETTINGS.get("auto_fetch"): + if not chat["hide_redo"]: + if ss.SETTINGS.get("show_redo"): + if len(chat["messages"]) > 1: + if chat["messages"][-1]["author"] == "assistant": + with cols[(cols_pos := cols_pos + 1)]: + st.button("", icon=":material/redo:", on_click=button_redo, use_container_width=True) + + if ss.SETTINGS.get("show_more"): + with cols[(cols_pos := cols_pos + 1)]: + st.button("", icon=":material/more_horiz:", on_click=button_more, use_container_width=True) + + if not chat["hide_fetch_button"]: + if ss.SETTINGS.get("show_fetch_button"): with cols[(cols_pos := cols_pos + 1)]: st.button("", icon=":material/skip_next:", on_click=button_fetch, use_container_width=True) - if ss.SETTINGS.get("show_fetch_toggle"): - with cols[(cols_pos := cols_pos + 1)]: - ss.SETTINGS.widget(st, st.toggle, "On", "auto_fetch") + if not chat["hide_fetch_toggle"]: + if ss.SETTINGS.get("show_fetch_toggle"): + with cols[(cols_pos := cols_pos + 1)]: + ss.SETTINGS.widget(st, st.toggle, "On", "fetch_reply") diff --git a/views/more_about.py b/views/more_about.py index 3cf51ab..d716948 100644 --- a/views/more_about.py +++ b/views/more_about.py @@ -22,4 +22,8 @@ import streamlit as st ss = st.session_state def more_about(): - st.write("About") + try: + with open("README.md") as f: + st.markdown(f.read()) + except: + pass diff --git a/views/more_settings.py b/views/more_settings.py index 11d85cc..e9ecfc2 100644 --- a/views/more_settings.py +++ b/views/more_settings.py @@ -21,37 +21,62 @@ import streamlit as st ss = st.session_state +def more_settings_account_tab(): + cols = st.columns([1, 1]) + with cols[0]: + st.caption("Account") + with st.container(border=False): + ss.SETTINGS.widget(st, st.text_input, "Name", "account_name") + ss.SETTINGS.widget(st, st.text_input, "E-mail", "account_email") + st.write("") + +def more_settings_general_tab(): + cols = st.columns([1, 1]) + with cols[0]: + st.caption("Behavior") + with st.container(border=False): + ss.SETTINGS.widget(st, st.toggle, "Fetch reply", "fetch_reply") + st.write("") + + st.caption("Interface") + with st.container(border=False): + ss.SETTINGS.widget(st, st.toggle, "Show clear", "show_clear") + ss.SETTINGS.widget(st, st.toggle, "Show undo", "show_undo") + ss.SETTINGS.widget(st, st.toggle, "Show redo", "show_redo") + ss.SETTINGS.widget(st, st.toggle, "Show more", "show_more") + ss.SETTINGS.widget(st, st.toggle, "Show fetch button", "show_fetch_button") + ss.SETTINGS.widget(st, st.toggle, "Show fetch toggle", "show_fetch_toggle") + st.write("") + +def more_settings_advanced_tab(): + cols = st.columns([1, 1]) + with cols[0]: + st.caption("Advanced") + with st.container(border=False): + ss.APP_SETTINGS.widget(st, st.text_input, "Inference Server URL", "inference_server_url", help="The URL to POST to for text-based inference") + st.write("") + def more_settings(): - if len(ss.TOKEN_LIST) > 1: - st.caption("Account") - with st.container(border=True): - - def save_token(): - set_cookie("token", ss.new_token) - page_redirect() + if ss.TOKEN is None: + st.write("Settings are available only for authenticated users") + return - token = st.text_input( - "Token", - value=get_cookie("token"), - help="Provide a valid token here", - on_change=save_token, - key="new_token") + tab_labels = ["Account", "General"] - if ss.TOKEN is None: - return + if ss.IS_ADMIN: + tab_labels = tab_labels + ["Advanced"] - st.caption("Behavior") - with st.container(border=True): - ss.SETTINGS.widget(st, st.toggle, "Fetch reply automatically", "auto_fetch") - ss.SETTINGS.widget(st, st.toggle, "Save as copy by default", "save_as") + tabs = st.tabs(tab_labels) + + if (t := "Account") in tab_labels: + with tabs[tab_labels.index(t)]: + more_settings_account_tab() - st.caption("Display") - with st.container(border=True): - st.caption("Toolbar") - ss.SETTINGS.widget(st, st.toggle, "Show clear button", "show_clear") - ss.SETTINGS.widget(st, st.toggle, "Show undo button", "show_undo") - ss.SETTINGS.widget(st, st.toggle, "Show redo button", "show_redo") - ss.SETTINGS.widget(st, st.toggle, "Show more button", "show_more") - ss.SETTINGS.widget(st, st.toggle, "Show fetch button", "show_fetch_button") - ss.SETTINGS.widget(st, st.toggle, "Show fetch toggle", "show_fetch_toggle") + if (t := "General") in tab_labels: + with tabs[tab_labels.index(t)]: + more_settings_general_tab() + + if (t := "Advanced") in tab_labels: + with tabs[tab_labels.index(t)]: + more_settings_advanced_tab()