Completed per-conversation setting overrides

This commit is contained in:
Conner Harkness 2025-06-03 19:24:56 -06:00
parent 25beca85e9
commit 1107eba42b
5 changed files with 190 additions and 99 deletions

37
app.py
View File

@ -67,7 +67,7 @@ if ss.TOKEN is not None:
break break
ss.CHATS_DIR = f"{ss.CONFIG_DIR}/chats" 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, "fetch_reply": True,
"save_as": False, "save_as": False,
"show_clear": False, "show_clear": False,
@ -76,6 +76,14 @@ ss.SETTINGS = JsonFile(f"{ss.CONFIG_DIR}/settings.json", defaults={
"show_more": True, "show_more": True,
"show_fetch_button": True, "show_fetch_button": True,
"show_fetch_toggle": 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.chats_default import *
from views.more_about import * from views.more_about import *
from views.more_settings 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", "About", more_about, icon=":material/info:")
register_page("More", "Settings", more_settings, icon=":material/settings:") 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") st.sidebar.caption("mllm-lite by caharkness")
# Only attempt to handle redirect after all page objects exist: # Only attempt to handle redirect after all page objects exist:

View File

@ -67,4 +67,7 @@ window.resize(1280, 640)
window.setCentralWidget(webview) window.setCentralWidget(webview)
window.show() window.show()
# Allow browser to control the window's title:
webview.titleChanged.connect(window.setWindowTitle)
sys.exit(app.exec()) sys.exit(app.exec())

View File

@ -23,6 +23,7 @@ import streamlit as st
ss = st.session_state ss = st.session_state
def chats_default(item): def chats_default(item):
chat = {} chat = {}
chat_path = item["path"] chat_path = item["path"]
chat_name = item["title"] chat_name = item["title"]
@ -40,31 +41,22 @@ def chats_default(item):
"keep": 0, "keep": 0,
"pinned": False, "pinned": False,
"auto_clear": False, "auto_clear": False,
#"custom_settings": False, "hide_clear": False,
#"auto_fetch": ss.SETTINGS.get("auto_fetch"), "hide_undo": False,
#"show_clear": ss.SETTINGS.get("show_clear"), "hide_redo": False,
#"show_undo": ss.SETTINGS.get("show_undo"), "hide_fetch_button": False,
#"show_redo": ss.SETTINGS.get("show_redo"), "hide_fetch_toggle": False
#"show_fetch_button": ss.SETTINGS.get("show_fetch_button"),
#"show_fetch_toggle": ss.SETTINGS.get("show_fetch_toggle"),
} }
#
#
#
def load_defaults(): def load_defaults():
for k in chat_defaults.keys(): for k in chat_defaults.keys():
if k not in chat.keys(): if k not in chat.keys():
chat[k] = chat_defaults[k] 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): def save_chat(name=chat_name, overwrite=True):
path = f"{ss.CHATS_DIR}/{name}.json" path = f"{ss.CHATS_DIR}/{name}.json"
path = get_next_filename(path) if not overwrite else path 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") st.caption("This is the beginning of the conversation")
for message in chat["messages"]: for message in chat["messages"]:
@ -102,7 +96,7 @@ def chats_default(item):
with st.chat_message("assistant"): with st.chat_message("assistant"):
def stream_reply(input_data): def stream_reply(input_data):
response = requests.post( response = requests.post(
f"http://127.0.0.1:11434/", ss.APP_SETTINGS.get("inference_server_url"),
data=input_data.encode("utf-8"), data=input_data.encode("utf-8"),
headers={"Content-Type": "text/plain"}, headers={"Content-Type": "text/plain"},
stream=True) stream=True)
@ -137,7 +131,7 @@ def chats_default(item):
save_chat() save_chat()
if ss.SETTINGS.get("auto_fetch"): if ss.SETTINGS.get("fetch_reply"):
st.session_state.run = 1 st.session_state.run = 1
st.rerun() st.rerun()
@ -167,55 +161,85 @@ def chats_default(item):
st.session_state.run = 1 st.session_state.run = 1
def button_more(): def button_more():
@st.dialog("More") @st.dialog(chat_name)
def button_more_modal(): def button_more_modal():
tab_labels = ["General", "More"] tab_labels = ["General", "Advanced", "Interface"]
tabs = st.tabs(tab_labels) tabs = st.tabs(tab_labels)
save_button_group = None
action_button_group = None
if (t := "General") in tab_labels: if (t := "General") in tab_labels:
with tabs[tab_labels.index(t)]: with tabs[tab_labels.index(t)]:
original_name = chat_name original_name = chat_name
new_name = st.text_input("Name", value=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)]: 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") 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): 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_auto_clear = st.toggle("Auto clear", value=chat["auto_clear"])
new_pinned = st.toggle("Pinned", value=chat["pinned"]) new_pinned = st.toggle("Pinned", value=chat["pinned"])
cols = st.columns([1, 1, 1, 1]) action_button_group = st.container()
with cols[0]: if (t := "Interface") in tab_labels:
if st.button("Save", icon=":material/save:", use_container_width=True): with tabs[tab_labels.index(t)]:
chat["context"] = new_context new_hide_clear = st.toggle("Hide clear", value=chat["hide_clear"])
chat["keep"] = new_keep new_hide_undo = st.toggle("Hide undo", value=chat["hide_undo"])
chat["pinned"] = new_pinned new_hide_redo = st.toggle("Hide redo", value=chat["hide_redo"])
chat["auto_clear"] = new_auto_clear 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: if chat_name != new_name:
os.unlink(chat_path) os.unlink(chat_path)
redirect("Chats", goto_name) redirect("Chats", goto_name)
with cols[1]: with cols[1]:
if st.button("Clear", icon=":material/mop:", use_container_width=True): if st.button("Copy", icon=":material/file_copy:", use_container_width=True):
chat["keep"] = new_keep save_common()
clear_chat() goto_name = save_chat(name=new_name, overwrite=False)
save_chat() redirect("Chats", goto_name)
redirect("Chats", original_name)
with cols[2]:
if st.button("Delete", icon=":material/delete:", use_container_width=True):
os.unlink(chat_path)
st.rerun()
button_more_modal() button_more_modal()
@ -225,30 +249,36 @@ def chats_default(item):
cols = st.columns(7) cols = st.columns(7)
cols_pos = -1 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 not chat["hide_clear"]:
if len(chat["messages"]) > 0: if ss.SETTINGS.get("show_clear"):
with cols[(cols_pos := cols_pos + 1)]: if len(chat["messages"]) > abs(chat["keep"]):
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":
with cols[(cols_pos := cols_pos + 1)]: 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)]: if not chat["hide_undo"]:
st.button("", icon=":material/more_horiz:", on_click=button_more, 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_fetch_button"): if not chat["hide_redo"]:
if not ss.SETTINGS.get("auto_fetch"): 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)]: with cols[(cols_pos := cols_pos + 1)]:
st.button("", icon=":material/skip_next:", on_click=button_fetch, use_container_width=True) st.button("", icon=":material/skip_next:", on_click=button_fetch, use_container_width=True)
if ss.SETTINGS.get("show_fetch_toggle"): if not chat["hide_fetch_toggle"]:
with cols[(cols_pos := cols_pos + 1)]: if ss.SETTINGS.get("show_fetch_toggle"):
ss.SETTINGS.widget(st, st.toggle, "On", "auto_fetch") with cols[(cols_pos := cols_pos + 1)]:
ss.SETTINGS.widget(st, st.toggle, "On", "fetch_reply")

View File

@ -22,4 +22,8 @@ import streamlit as st
ss = st.session_state ss = st.session_state
def more_about(): def more_about():
st.write("About") try:
with open("README.md") as f:
st.markdown(f.read())
except:
pass

View File

@ -21,37 +21,62 @@ import streamlit as st
ss = st.session_state 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(): def more_settings():
if len(ss.TOKEN_LIST) > 1: if ss.TOKEN is None:
st.caption("Account") st.write("Settings are available only for authenticated users")
with st.container(border=True): return
def save_token():
set_cookie("token", ss.new_token)
page_redirect()
token = st.text_input( tab_labels = ["Account", "General"]
"Token",
value=get_cookie("token"),
help="Provide a valid token here",
on_change=save_token,
key="new_token")
if ss.TOKEN is None: if ss.IS_ADMIN:
return tab_labels = tab_labels + ["Advanced"]
st.caption("Behavior") tabs = st.tabs(tab_labels)
with st.container(border=True):
ss.SETTINGS.widget(st, st.toggle, "Fetch reply automatically", "auto_fetch") if (t := "Account") in tab_labels:
ss.SETTINGS.widget(st, st.toggle, "Save as copy by default", "save_as") with tabs[tab_labels.index(t)]:
more_settings_account_tab()
st.caption("Display") if (t := "General") in tab_labels:
with st.container(border=True): with tabs[tab_labels.index(t)]:
st.caption("Toolbar") more_settings_general_tab()
ss.SETTINGS.widget(st, st.toggle, "Show clear button", "show_clear")
ss.SETTINGS.widget(st, st.toggle, "Show undo button", "show_undo") if (t := "Advanced") in tab_labels:
ss.SETTINGS.widget(st, st.toggle, "Show redo button", "show_redo") with tabs[tab_labels.index(t)]:
ss.SETTINGS.widget(st, st.toggle, "Show more button", "show_more") more_settings_advanced_tab()
ss.SETTINGS.widget(st, st.toggle, "Show fetch button", "show_fetch_button")
ss.SETTINGS.widget(st, st.toggle, "Show fetch toggle", "show_fetch_toggle")