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:
@@ -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
|
||||
|
@@ -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:
|
||||
|
26
libs/ktem/ktem/pages/chat/chat_suggestion.py
Normal file
26
libs/ktem/ktem/pages/chat/chat_suggestion.py
Normal 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
|
@@ -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()
|
||||
|
@@ -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_,
|
||||
},
|
||||
|
@@ -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:
|
||||
|
@@ -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:
|
||||
|
Reference in New Issue
Block a user