Fix UI bugs (#8)

* Auto create conversation when the user starts

* Add conversation rename rule check

* Fix empty name during save

* Confirm deleting conversation

* Show warning if users don't select file when upload files in the File Index

* Feedback when user uploads duplicated file

* Limit the file types

* Fix valid username

* Allow login when username with leading and trailing whitespaces

* Improve the user

* Disable admin panel for non-admnin user

* Refresh user lists after creating/deleting users

* Auto logging in

* Clear admin information upon signing out

* Fix unable to receive uploaded filename that include special characters, like !@#$%^&*().pdf

* Set upload validation for FileIndex

* Improve user management UI/UIX

* Show extraction error when indexing file

* Return selected user -1 when signing out

* Fix default supported file types in file index

* Validate changing password

* Allow the selector to contain mulitple gradio components

* A more tolerable placeholder screen

* Allow chat suggestion box

* Increase concurrency limit

* Make adobe loader optional

* Use BaseReasoning

---------

Co-authored-by: trducng <trungduc1992@gmail.com>
This commit is contained in:
ian_Cin
2024-04-03 16:33:54 +07:00
committed by GitHub
parent 43a18ba070
commit ecf09b275f
23 changed files with 936 additions and 255 deletions

View File

@@ -40,7 +40,7 @@ def validate_username(usn):
if len(usn) > 32:
errors.append("Username must be at most 32 characters long")
if not usn.strip("_").isalnum():
if not usn.replace("_", "").isalnum():
errors.append(
"Username must contain only alphanumeric characters and underscores"
)
@@ -97,8 +97,6 @@ def validate_password(pwd, pwd_cnf):
class UserManagement(BasePage):
def __init__(self, app):
self._app = app
self.selected_panel_false = "Selected user: (please select above)"
self.selected_panel_true = "Selected user: {name}"
self.on_building_ui()
if hasattr(flowsettings, "KH_FEATURE_USER_MANAGEMENT_ADMIN") and hasattr(
@@ -126,7 +124,38 @@ class UserManagement(BasePage):
gr.Info(f'User "{usn}" created successfully')
def on_building_ui(self):
with gr.Accordion(label="Create user", open=False):
with gr.Tab(label="User list"):
self.state_user_list = gr.State(value=None)
self.user_list = gr.DataFrame(
headers=["id", "name", "admin"],
interactive=False,
)
with gr.Group(visible=False) as self._selected_panel:
self.selected_user_id = gr.Number(value=-1, visible=False)
self.usn_edit = gr.Textbox(label="Username")
with gr.Row():
self.pwd_edit = gr.Textbox(label="Change password", type="password")
self.pwd_cnf_edit = gr.Textbox(
label="Confirm change password",
type="password",
)
self.admin_edit = gr.Checkbox(label="Admin")
with gr.Row() as self._selected_panel_btn:
with gr.Column():
self.btn_edit_save = gr.Button("Save")
with gr.Column():
self.btn_delete = gr.Button("Delete")
with gr.Row():
self.btn_delete_yes = gr.Button(
"Confirm delete", variant="primary", visible=False
)
self.btn_delete_no = gr.Button("Cancel", visible=False)
with gr.Column():
self.btn_close = gr.Button("Close")
with gr.Tab(label="Create user"):
self.usn_new = gr.Textbox(label="Username", interactive=True)
self.pwd_new = gr.Textbox(
label="Password", type="password", interactive=True
@@ -139,52 +168,28 @@ class UserManagement(BasePage):
gr.Markdown(PASSWORD_RULE)
self.btn_new = gr.Button("Create user")
gr.Markdown("## User list")
self.btn_list_user = gr.Button("Refresh user list")
self.state_user_list = gr.State(value=None)
self.user_list = gr.DataFrame(
headers=["id", "name", "admin"],
interactive=False,
)
with gr.Row():
self.selected_user_id = gr.State(value=None)
self.selected_panel = gr.Markdown(self.selected_panel_false)
self.deselect_button = gr.Button("Deselect", visible=False)
with gr.Group():
self.btn_delete = gr.Button("Delete user")
with gr.Row():
self.btn_delete_yes = gr.Button("Confirm", visible=False)
self.btn_delete_no = gr.Button("Cancel", visible=False)
gr.Markdown("## User details")
self.usn_edit = gr.Textbox(label="Username")
self.pwd_edit = gr.Textbox(label="Password", type="password")
self.pwd_cnf_edit = gr.Textbox(label="Confirm password", type="password")
self.admin_edit = gr.Checkbox(label="Admin")
self.btn_edit_save = gr.Button("Save")
def on_register_events(self):
self.btn_new.click(
self.create_user,
inputs=[self.usn_new, self.pwd_new, self.pwd_cnf_new],
outputs=None,
)
self.btn_list_user.click(
self.list_users, inputs=None, outputs=[self.state_user_list, self.user_list]
outputs=[self.usn_new, self.pwd_new, self.pwd_cnf_new],
).then(
self.list_users,
inputs=self._app.user_id,
outputs=[self.state_user_list, self.user_list],
)
self.user_list.select(
self.select_user,
inputs=self.user_list,
outputs=[self.selected_user_id, self.selected_panel],
outputs=[self.selected_user_id],
show_progress="hidden",
)
self.selected_panel.change(
self.selected_user_id.change(
self.on_selected_user_change,
inputs=[self.selected_user_id],
outputs=[
self.deselect_button,
self._selected_panel,
self._selected_panel_btn,
# delete section
self.btn_delete,
self.btn_delete_yes,
@@ -197,12 +202,6 @@ class UserManagement(BasePage):
],
show_progress="hidden",
)
self.deselect_button.click(
lambda: (None, self.selected_panel_false),
inputs=None,
outputs=[self.selected_user_id, self.selected_panel],
show_progress="hidden",
)
self.btn_delete.click(
self.on_btn_delete_click,
inputs=[self.selected_user_id],
@@ -211,9 +210,13 @@ class UserManagement(BasePage):
)
self.btn_delete_yes.click(
self.delete_user,
inputs=[self.selected_user_id],
outputs=[self.selected_user_id, self.selected_panel],
inputs=[self._app.user_id, self.selected_user_id],
outputs=[self.selected_user_id],
show_progress="hidden",
).then(
self.list_users,
inputs=self._app.user_id,
outputs=[self.state_user_list, self.user_list],
)
self.btn_delete_no.click(
lambda: (
@@ -234,21 +237,53 @@ class UserManagement(BasePage):
self.pwd_cnf_edit,
self.admin_edit,
],
outputs=None,
outputs=[self.pwd_edit, self.pwd_cnf_edit],
show_progress="hidden",
).then(
self.list_users,
inputs=self._app.user_id,
outputs=[self.state_user_list, self.user_list],
)
self.btn_close.click(
lambda: -1,
outputs=[self.selected_user_id],
)
def on_subscribe_public_events(self):
self._app.subscribe_event(
name="onSignIn",
definition={
"fn": self.list_users,
"inputs": [self._app.user_id],
"outputs": [self.state_user_list, self.user_list],
},
)
self._app.subscribe_event(
name="onSignOut",
definition={
"fn": lambda: ("", "", "", None, None, -1),
"outputs": [
self.usn_new,
self.pwd_new,
self.pwd_cnf_new,
self.state_user_list,
self.user_list,
self.selected_user_id,
],
},
)
def create_user(self, usn, pwd, pwd_cnf):
errors = validate_username(usn)
if errors:
gr.Warning(errors)
return
return usn, pwd, pwd_cnf
errors = validate_password(pwd, pwd_cnf)
print(errors)
if errors:
gr.Warning(errors)
return
return usn, pwd, pwd_cnf
with Session(engine) as session:
statement = select(User).where(User.username_lower == usn.lower())
@@ -265,8 +300,22 @@ class UserManagement(BasePage):
session.commit()
gr.Info(f'User "{usn}" created successfully')
def list_users(self):
return "", "", ""
def list_users(self, user_id):
if user_id is None:
return [], pd.DataFrame.from_records(
[{"id": "-", "username": "-", "admin": "-"}]
)
with Session(engine) as session:
statement = select(User).where(User.id == user_id)
user = session.exec(statement).one()
if not user.admin:
return [], pd.DataFrame.from_records(
[{"id": "-", "username": "-", "admin": "-"}]
)
statement = select(User)
results = [
{"id": user.id, "username": user.username, "admin": user.admin}
@@ -284,18 +333,17 @@ class UserManagement(BasePage):
def select_user(self, user_list, ev: gr.SelectData):
if ev.value == "-" and ev.index[0] == 0:
gr.Info("No user is loaded. Please refresh the user list")
return None, self.selected_panel_false
return -1
if not ev.selected:
return None, self.selected_panel_false
return -1
return user_list["id"][ev.index[0]], self.selected_panel_true.format(
name=user_list["username"][ev.index[0]]
)
return user_list["id"][ev.index[0]]
def on_selected_user_change(self, selected_user_id):
if selected_user_id is None:
deselect_button = gr.update(visible=False)
if selected_user_id == -1:
_selected_panel = gr.update(visible=False)
_selected_panel_btn = gr.update(visible=False)
btn_delete = gr.update(visible=True)
btn_delete_yes = gr.update(visible=False)
btn_delete_no = gr.update(visible=False)
@@ -304,7 +352,8 @@ class UserManagement(BasePage):
pwd_cnf_edit = gr.update(value="")
admin_edit = gr.update(value=False)
else:
deselect_button = gr.update(visible=True)
_selected_panel = gr.update(visible=True)
_selected_panel_btn = gr.update(visible=True)
btn_delete = gr.update(visible=True)
btn_delete_yes = gr.update(visible=False)
btn_delete_no = gr.update(visible=False)
@@ -319,7 +368,8 @@ class UserManagement(BasePage):
admin_edit = gr.update(value=user.admin)
return (
deselect_button,
_selected_panel,
_selected_panel_btn,
btn_delete,
btn_delete_yes,
btn_delete_no,
@@ -344,17 +394,16 @@ class UserManagement(BasePage):
return btn_delete, btn_delete_yes, btn_delete_no
def save_user(self, selected_user_id, usn, pwd, pwd_cnf, admin):
if usn:
errors = validate_username(usn)
if errors:
gr.Warning(errors)
return
errors = validate_username(usn)
if errors:
gr.Warning(errors)
return pwd, pwd_cnf
if pwd:
errors = validate_password(pwd, pwd_cnf)
if errors:
gr.Warning(errors)
return
return pwd, pwd_cnf
with Session(engine) as session:
statement = select(User).where(User.id == int(selected_user_id))
@@ -367,11 +416,17 @@ class UserManagement(BasePage):
session.commit()
gr.Info(f'User "{usn}" updated successfully')
def delete_user(self, selected_user_id):
return "", ""
def delete_user(self, current_user, selected_user_id):
if current_user == selected_user_id:
gr.Warning("You cannot delete yourself")
return selected_user_id
with Session(engine) as session:
statement = select(User).where(User.id == int(selected_user_id))
user = session.exec(statement).one()
session.delete(user)
session.commit()
gr.Info(f'User "{user.username}" deleted successfully')
return None, self.selected_panel_false
return -1

View File

@@ -7,8 +7,10 @@ from ktem.app import BasePage
from ktem.components import reasonings
from ktem.db.models import Conversation, engine
from sqlmodel import Session, select
from theflow.settings import settings as flowsettings
from .chat_panel import ChatPanel
from .chat_suggestion import ChatSuggestion
from .common import STATE
from .control import ConversationControl
from .report import ReportIssue
@@ -26,24 +28,39 @@ class ChatPage(BasePage):
with gr.Column(scale=1):
self.chat_control = ConversationControl(self._app)
if getattr(flowsettings, "KH_FEATURE_CHAT_SUGGESTION", False):
self.chat_suggestion = ChatSuggestion(self._app)
for index in self._app.index_manager.indices:
index.selector = -1
index.selector = None
index_ui = index.get_selector_component_ui()
if not index_ui:
# the index doesn't have a selector UI component
continue
index_ui.unrender()
index_ui.unrender() # need to rerender later within Accordion
with gr.Accordion(label=f"{index.name} Index", open=False):
index_ui.render()
gr_index = index_ui.as_gradio_component()
if gr_index:
index.selector = len(self._indices_input)
self._indices_input.append(gr_index)
if isinstance(gr_index, list):
index.selector = tuple(
range(
len(self._indices_input),
len(self._indices_input) + len(gr_index),
)
)
self._indices_input.extend(gr_index)
else:
index.selector = len(self._indices_input)
self._indices_input.append(gr_index)
setattr(self, f"_index_{index.id}", index_ui)
self.report_issue = ReportIssue(self._app)
with gr.Column(scale=6):
self.chat_panel = ChatPanel(self._app)
with gr.Column(scale=3):
with gr.Accordion(label="Information panel", open=True):
self.info_panel = gr.HTML(elem_id="chat-info-panel")
@@ -54,11 +71,24 @@ class ChatPage(BasePage):
self.chat_panel.text_input.submit,
self.chat_panel.submit_btn.click,
],
fn=self.chat_panel.submit_msg,
inputs=[self.chat_panel.text_input, self.chat_panel.chatbot],
outputs=[self.chat_panel.text_input, self.chat_panel.chatbot],
fn=self.submit_msg,
inputs=[
self.chat_panel.text_input,
self.chat_panel.chatbot,
self._app.user_id,
self.chat_control.conversation_id,
self.chat_control.conversation_rn,
],
outputs=[
self.chat_panel.text_input,
self.chat_panel.chatbot,
self.chat_control.conversation_id,
self.chat_control.conversation,
self.chat_control.conversation_rn,
],
concurrency_limit=20,
show_progress="hidden",
).then(
).success(
fn=self.chat_fn,
inputs=[
self.chat_control.conversation_id,
@@ -72,6 +102,7 @@ class ChatPage(BasePage):
self.info_panel,
self.chat_state,
],
concurrency_limit=20,
show_progress="minimal",
).then(
fn=self.update_data_source,
@@ -82,6 +113,7 @@ class ChatPage(BasePage):
]
+ self._indices_input,
outputs=None,
concurrency_limit=20,
)
self.chat_panel.regen_btn.click(
@@ -98,6 +130,7 @@ class ChatPage(BasePage):
self.info_panel,
self.chat_state,
],
concurrency_limit=20,
show_progress="minimal",
).then(
fn=self.update_data_source,
@@ -108,6 +141,7 @@ class ChatPage(BasePage):
]
+ self._indices_input,
outputs=None,
concurrency_limit=20,
)
self.chat_panel.chatbot.like(
@@ -116,7 +150,12 @@ class ChatPage(BasePage):
outputs=None,
)
self.chat_control.conversation.change(
self.chat_control.btn_new.click(
self.chat_control.new_conv,
inputs=self._app.user_id,
outputs=[self.chat_control.conversation_id, self.chat_control.conversation],
show_progress="hidden",
).then(
self.chat_control.select_conv,
inputs=[self.chat_control.conversation],
outputs=[
@@ -124,12 +163,71 @@ class ChatPage(BasePage):
self.chat_control.conversation,
self.chat_control.conversation_rn,
self.chat_panel.chatbot,
self.info_panel,
self.chat_state,
]
+ self._indices_input,
show_progress="hidden",
)
self.chat_control.btn_del.click(
lambda id: self.toggle_delete(id),
inputs=[self.chat_control.conversation_id],
outputs=[self.chat_control._new_delete, self.chat_control._delete_confirm],
)
self.chat_control.btn_del_conf.click(
self.chat_control.delete_conv,
inputs=[self.chat_control.conversation_id, self._app.user_id],
outputs=[self.chat_control.conversation_id, self.chat_control.conversation],
show_progress="hidden",
).then(
self.chat_control.select_conv,
inputs=[self.chat_control.conversation],
outputs=[
self.chat_control.conversation_id,
self.chat_control.conversation,
self.chat_control.conversation_rn,
self.chat_panel.chatbot,
self.info_panel,
]
+ self._indices_input,
show_progress="hidden",
).then(
lambda: self.toggle_delete(""),
outputs=[self.chat_control._new_delete, self.chat_control._delete_confirm],
)
self.chat_control.btn_del_cnl.click(
lambda: self.toggle_delete(""),
outputs=[self.chat_control._new_delete, self.chat_control._delete_confirm],
)
self.chat_control.conversation_rn_btn.click(
self.chat_control.rename_conv,
inputs=[
self.chat_control.conversation_id,
self.chat_control.conversation_rn,
self._app.user_id,
],
outputs=[self.chat_control.conversation, self.chat_control.conversation],
show_progress="hidden",
)
self.chat_control.conversation.select(
self.chat_control.select_conv,
inputs=[self.chat_control.conversation],
outputs=[
self.chat_control.conversation_id,
self.chat_control.conversation,
self.chat_control.conversation_rn,
self.chat_panel.chatbot,
self.info_panel,
]
+ self._indices_input,
show_progress="hidden",
).then(
lambda: self.toggle_delete(""),
outputs=[self.chat_control._new_delete, self.chat_control._delete_confirm],
)
self.report_issue.report_btn.click(
self.report_issue.report,
inputs=[
@@ -140,11 +238,77 @@ class ChatPage(BasePage):
self.chat_panel.chatbot,
self._app.settings_state,
self._app.user_id,
self.info_panel,
self.chat_state,
]
+ self._indices_input,
outputs=None,
)
if getattr(flowsettings, "KH_FEATURE_CHAT_SUGGESTION", False):
self.chat_suggestion.example.select(
self.chat_suggestion.select_example,
outputs=[self.chat_panel.text_input],
show_progress="hidden",
)
def submit_msg(self, chat_input, chat_history, user_id, conv_id, conv_name):
"""Submit a message to the chatbot"""
if not chat_input:
raise ValueError("Input is empty")
if not conv_id:
id_, update = self.chat_control.new_conv(user_id)
with Session(engine) as session:
statement = select(Conversation).where(Conversation.id == id_)
name = session.exec(statement).one().name
new_conv_id = id_
conv_update = update
new_conv_name = name
else:
new_conv_id = conv_id
conv_update = gr.update()
new_conv_name = conv_name
return (
"",
chat_history + [(chat_input, None)],
new_conv_id,
conv_update,
new_conv_name,
)
def toggle_delete(self, conv_id):
if conv_id:
return gr.update(visible=False), gr.update(visible=True)
else:
return gr.update(visible=True), gr.update(visible=False)
def on_subscribe_public_events(self):
if self._app.f_user_management:
self._app.subscribe_event(
name="onSignIn",
definition={
"fn": self.chat_control.reload_conv,
"inputs": [self._app.user_id],
"outputs": [self.chat_control.conversation],
"show_progress": "hidden",
},
)
self._app.subscribe_event(
name="onSignOut",
definition={
"fn": lambda: self.chat_control.select_conv(""),
"outputs": [
self.chat_control.conversation_id,
self.chat_control.conversation,
self.chat_control.conversation_rn,
self.chat_panel.chatbot,
]
+ self._indices_input,
"show_progress": "hidden",
},
)
def update_data_source(self, convo_id, messages, state, *selecteds):
"""Update the data source"""
@@ -154,8 +318,12 @@ class ChatPage(BasePage):
selecteds_ = {}
for index in self._app.index_manager.indices:
if index.selector != -1:
if index.selector is None:
continue
if isinstance(index.selector, int):
selecteds_[str(index.id)] = selecteds[index.selector]
else:
selecteds_[str(index.id)] = [selecteds[i] for i in index.selector]
with Session(engine) as session:
statement = select(Conversation).where(Conversation.id == convo_id)
@@ -205,8 +373,11 @@ class ChatPage(BasePage):
retrievers = []
for index in self._app.index_manager.indices:
index_selected = []
if index.selector != -1:
if isinstance(index.selector, int):
index_selected = selecteds[index.selector]
if isinstance(index.selector, tuple):
for i in index.selector:
index_selected.append(selecteds[i])
iretrievers = index.get_retriever_pipelines(settings, index_selected)
retrievers += iretrievers
@@ -250,7 +421,10 @@ class ChatPage(BasePage):
break
if "output" in response:
text += response["output"]
if response["output"] is None:
text = ""
else:
text += response["output"]
if "evidence" in response:
if response["evidence"] is None:

View File

@@ -0,0 +1,26 @@
import gradio as gr
from ktem.app import BasePage
from theflow.settings import settings as flowsettings
class ChatSuggestion(BasePage):
def __init__(self, app):
self._app = app
self.on_building_ui()
def on_building_ui(self):
chat_samples = getattr(flowsettings, "KH_FEATURE_CHAT_SUGGESTION_SAMPLES", [])
chat_samples = [[each] for each in chat_samples]
with gr.Accordion(label="Chat Suggestion", open=False) as self.accordion:
self.example = gr.DataFrame(
value=chat_samples,
headers=["Sample"],
interactive=False,
wrap=True,
)
def as_gradio_component(self):
return self.example
def select_example(self, ev: gr.SelectData):
return ev.value

View File

@@ -10,6 +10,17 @@ from .common import STATE
logger = logging.getLogger(__name__)
def is_conv_name_valid(name):
"""Check if the conversation name is valid"""
errors = []
if len(name) == 0:
errors.append("Name cannot be empty")
elif len(name) > 40:
errors.append("Name cannot be longer than 40 characters")
return "; ".join(errors)
class ConversationControl(BasePage):
"""Manage conversation"""
@@ -28,9 +39,17 @@ class ConversationControl(BasePage):
interactive=True,
)
with gr.Row():
self.conversation_new_btn = gr.Button(value="New", min_width=10)
self.conversation_del_btn = gr.Button(value="Delete", min_width=10)
with gr.Row() as self._new_delete:
self.btn_new = gr.Button(value="New", min_width=10)
self.btn_del = gr.Button(value="Delete", min_width=10)
with gr.Row(visible=False) as self._delete_confirm:
self.btn_del_conf = gr.Button(
value="Delete",
variant="primary",
min_width=10,
)
self.btn_del_cnl = gr.Button(value="Cancel", min_width=10)
with gr.Row():
self.conversation_rn = gr.Text(
@@ -52,48 +71,6 @@ class ConversationControl(BasePage):
# outputs=[current_state],
# )
def on_subscribe_public_events(self):
if self._app.f_user_management:
self._app.subscribe_event(
name="onSignIn",
definition={
"fn": self.reload_conv,
"inputs": [self._app.user_id],
"outputs": [self.conversation],
"show_progress": "hidden",
},
)
self._app.subscribe_event(
name="onSignOut",
definition={
"fn": self.reload_conv,
"inputs": [self._app.user_id],
"outputs": [self.conversation],
"show_progress": "hidden",
},
)
def on_register_events(self):
self.conversation_new_btn.click(
self.new_conv,
inputs=self._app.user_id,
outputs=[self.conversation_id, self.conversation],
show_progress="hidden",
)
self.conversation_del_btn.click(
self.delete_conv,
inputs=[self.conversation_id, self._app.user_id],
outputs=[self.conversation_id, self.conversation],
show_progress="hidden",
)
self.conversation_rn_btn.click(
self.rename_conv,
inputs=[self.conversation_id, self.conversation_rn, self._app.user_id],
outputs=[self.conversation, self.conversation],
show_progress="hidden",
)
def load_chat_history(self, user_id):
"""Reload chat history"""
options = []
@@ -112,7 +89,7 @@ class ConversationControl(BasePage):
def reload_conv(self, user_id):
conv_list = self.load_chat_history(user_id)
if conv_list:
return gr.update(value=conv_list[0][1], choices=conv_list)
return gr.update(value=None, choices=conv_list)
else:
return gr.update(value=None, choices=[])
@@ -133,10 +110,15 @@ class ConversationControl(BasePage):
return id_, gr.update(value=id_, choices=history)
def delete_conv(self, conversation_id, user_id):
"""Create new chat"""
"""Delete the selected conversation"""
if not conversation_id:
gr.Warning("No conversation selected.")
return None, gr.update()
if user_id is None:
gr.Warning("Please sign in first (Settings → User Settings)")
return None, gr.update()
with Session(engine) as session:
statement = select(Conversation).where(Conversation.id == conversation_id)
result = session.exec(statement).one()
@@ -161,6 +143,7 @@ class ConversationControl(BasePage):
name = result.name
selected = result.data_source.get("selected", {})
chats = result.data_source.get("messages", [])
info_panel = ""
state = result.data_source.get("state", STATE)
except Exception as e:
logger.warning(e)
@@ -168,22 +151,36 @@ class ConversationControl(BasePage):
name = ""
selected = {}
chats = []
info_panel = ""
state = STATE
indices = []
for index in self._app.index_manager.indices:
# assume that the index has selector
if index.selector == -1:
if index.selector is None:
continue
indices.append(selected.get(str(index.id), []))
if isinstance(index.selector, int):
indices.append(selected.get(str(index.id), []))
if isinstance(index.selector, tuple):
indices.extend(selected.get(str(index.id), [[]] * len(index.selector)))
return id_, id_, name, chats, state, *indices
return id_, id_, name, chats, info_panel, state, *indices
def rename_conv(self, conversation_id, new_name, user_id):
"""Rename the conversation"""
if user_id is None:
gr.Warning("Please sign in first (Settings → User Settings)")
return gr.update(), ""
if not conversation_id:
gr.Warning("No conversation selected.")
return gr.update(), ""
errors = is_conv_name_valid(new_name)
if errors:
gr.Warning(errors)
return gr.update(), conversation_id
with Session(engine) as session:
statement = select(Conversation).where(Conversation.id == conversation_id)
result = session.exec(statement).one()

View File

@@ -48,13 +48,19 @@ class ReportIssue(BasePage):
chat_history: list,
settings: dict,
user_id: Optional[int],
info_panel: str,
chat_state: dict,
*selecteds
*selecteds,
):
selecteds_ = {}
for index in self._app.index_manager.indices:
if index.selector != -1:
selecteds_[str(index.id)] = selecteds[index.selector]
if index.selector is not None:
if isinstance(index.selector, int):
selecteds_[str(index.id)] = selecteds[index.selector]
elif isinstance(index.selector, tuple):
selecteds_[str(index.id)] = [selecteds[_] for _ in index.selector]
else:
print(f"Unknown selector type: {index.selector}")
with Session(engine) as session:
issue = IssueReport(
@@ -66,6 +72,7 @@ class ReportIssue(BasePage):
chat={
"conv_id": conv_id,
"chat_history": chat_history,
"info_panel": info_panel,
"chat_state": chat_state,
"selecteds": selecteds_,
},

View File

@@ -31,11 +31,10 @@ class LoginPage(BasePage):
self.on_building_ui()
def on_building_ui(self):
gr.Markdown("Welcome to Kotaemon")
self.usn = gr.Textbox(label="Username")
self.pwd = gr.Textbox(label="Password", type="password")
self.btn_login = gr.Button("Login")
self._dummy = gr.State()
gr.Markdown("# Welcome to Kotaemon")
self.usn = gr.Textbox(label="Username", visible=False)
self.pwd = gr.Textbox(label="Password", type="password", visible=False)
self.btn_login = gr.Button("Login", visible=False)
def on_register_events(self):
onSignIn = gr.on(
@@ -45,24 +44,56 @@ class LoginPage(BasePage):
outputs=[self._app.user_id, self.usn, self.pwd],
show_progress="hidden",
js=signin_js,
).then(
self.toggle_login_visibility,
inputs=[self._app.user_id],
outputs=[self.usn, self.pwd, self.btn_login],
)
for event in self._app.get_event("onSignIn"):
onSignIn = onSignIn.success(**event)
def toggle_login_visibility(self, user_id):
return (
gr.update(visible=user_id is None),
gr.update(visible=user_id is None),
gr.update(visible=user_id is None),
)
def _on_app_created(self):
self._app.app.load(
None,
inputs=None,
outputs=[self.usn, self.pwd],
onSignIn = self._app.app.load(
self.login,
inputs=[self.usn, self.pwd],
outputs=[self._app.user_id, self.usn, self.pwd],
show_progress="hidden",
js=fetch_creds,
).then(
self.toggle_login_visibility,
inputs=[self._app.user_id],
outputs=[self.usn, self.pwd, self.btn_login],
)
for event in self._app.get_event("onSignIn"):
onSignIn = onSignIn.success(**event)
def on_subscribe_public_events(self):
self._app.subscribe_event(
name="onSignOut",
definition={
"fn": self.toggle_login_visibility,
"inputs": [self._app.user_id],
"outputs": [self.usn, self.pwd, self.btn_login],
"show_progress": "hidden",
},
)
def login(self, usn, pwd):
if not usn or not pwd:
return None, usn, pwd
hashed_password = hashlib.sha256(pwd.encode()).hexdigest()
with Session(engine) as session:
stmt = select(User).where(
User.username_lower == usn.lower(), User.password == hashed_password
User.username_lower == usn.lower().strip(),
User.password == hashed_password,
)
result = session.exec(stmt).all()
if result:

View File

@@ -164,9 +164,14 @@ class SettingsPage(BasePage):
show_progress="hidden",
)
onSignOutClick = self.signout.click(
lambda: (None, "Current user: ___"),
lambda: (None, "Current user: ___", "", ""),
inputs=None,
outputs=[self._user_id, self.current_name],
outputs=[
self._user_id,
self.current_name,
self.password_change,
self.password_change_confirm,
],
show_progress="hidden",
js=signout_js,
).then(
@@ -192,8 +197,12 @@ class SettingsPage(BasePage):
self.password_change_btn = gr.Button("Change password", interactive=True)
def change_password(self, user_id, password, password_confirm):
if password != password_confirm:
gr.Warning("Password does not match")
from ktem.pages.admin.user import validate_password
errors = validate_password(password, password_confirm)
if errors:
print(errors)
gr.Warning(errors)
return password, password_confirm
with Session(engine) as session: