diff --git a/libs/kotaemon/pyproject.toml b/libs/kotaemon/pyproject.toml index a02337e9f..85af159db 100644 --- a/libs/kotaemon/pyproject.toml +++ b/libs/kotaemon/pyproject.toml @@ -22,7 +22,7 @@ dependencies = [ "theflow", "llama-index>=0.9.0,<0.10.0", "llama-hub", - "gradio>=4.0.0", + "gradio>=4.0.0,<=4.22.0", "openpyxl", "cookiecutter", "click", diff --git a/libs/ktem/flowsettings.py b/libs/ktem/flowsettings.py index eaf71fadf..0284e255a 100644 --- a/libs/ktem/flowsettings.py +++ b/libs/ktem/flowsettings.py @@ -73,30 +73,29 @@ if config("OPENAI_API_KEY", default=""): KH_LLMS["openai"] = { - "def": { + "spec": { "__type__": "kotaemon.llms.ChatOpenAI", "temperature": 0, - "openai_api_base": config("OPENAI_API_BASE", default="") + "base_url": config("OPENAI_API_BASE", default="") or "https://api.openai.com/v1", - "openai_api_key": config("OPENAI_API_KEY", default=""), + "api_key": config("OPENAI_API_KEY", default=""), "model": config("OPENAI_CHAT_MODEL", default="") or "gpt-3.5-turbo", - "request_timeout": 10, - "stream": False, + "timeout": 10, }, "default": False, } if len(KH_EMBEDDINGS) < 1: KH_EMBEDDINGS["openai"] = { - "def": { + "spec": { "__type__": "kotaemon.embeddings.LCOpenAIEmbeddings", - "openai_api_base": config("OPENAI_API_BASE", default="") + "base_url": config("OPENAI_API_BASE", default="") or "https://api.openai.com/v1", - "openai_api_key": config("OPENAI_API_KEY", default=""), + "api_key": config("OPENAI_API_KEY", default=""), "model": config( "OPENAI_EMBEDDINGS_MODEL", default="text-embedding-ada-002" ) or "text-embedding-ada-002", - "request_timeout": 10, + "timeout": 10, "chunk_size": 16, }, "default": False, @@ -104,7 +103,7 @@ if config("LOCAL_MODEL", default=""): KH_LLMS["local"] = { - "def": { + "spec": { "__type__": "kotaemon.llms.EndpointChatLLM", "endpoint_url": "http://localhost:31415/v1/chat/completions", }, @@ -113,7 +112,7 @@ } if len(KH_EMBEDDINGS) < 1: KH_EMBEDDINGS["local"] = { - "def": { + "spec": { "__type__": "kotaemon.embeddings.EndpointEmbeddings", "endpoint_url": "http://localhost:31415/v1/embeddings", }, @@ -164,10 +163,4 @@ "config": {}, "index_type": "ktem.index.file.FileIndex", }, - { - "id": 2, - "name": "Sample", - "config": {}, - "index_type": "ktem.index.file.FileIndex", - }, ] diff --git a/libs/ktem/ktem/app.py b/libs/ktem/ktem/app.py index 64e8a9da0..3cb4e29a0 100644 --- a/libs/ktem/ktem/app.py +++ b/libs/ktem/ktem/app.py @@ -36,10 +36,7 @@ class BaseApp: def __init__(self): self.dev_mode = getattr(settings, "KH_MODE", "") == "dev" self.f_user_management = getattr(settings, "KH_FEATURE_USER_MANAGEMENT", False) - self._theme = gr.themes.Base( - font=("ui-sans-serif", "system-ui", "sans-serif"), - font_mono=("ui-monospace", "Consolas", "monospace"), - ) + self._theme = gr.Theme.from_hub("lone17/kotaemon") dir_assets = Path(__file__).parent / "assets" with (dir_assets / "css" / "main.css").open() as fi: diff --git a/libs/ktem/ktem/assets/css/main.css b/libs/ktem/ktem/assets/css/main.css index 025347866..16e3adc51 100644 --- a/libs/ktem/ktem/assets/css/main.css +++ b/libs/ktem/ktem/assets/css/main.css @@ -1,55 +1,91 @@ +/* no footer */ footer { - display: none !important; + display: none !important; +} + +/* customize scrollbar */ +::-webkit-scrollbar { + background: var(--background-fill-primary); +} +::-webkit-scrollbar-thumb { + background-color: var(--border-color-primary); + border: 4px solid transparent; + border-radius: 100px; + background-clip: content-box; +} +::-webkit-scrollbar-corner { + background: var(--border-color-primary); } .gradio-container { - max-width: 100% !important; - padding: 0 !important; + max-width: 100% !important; } +/* styling for header bar */ .header-bar { - background-color: #f7f7f7; - margin: 0px 0px 20px; - overflow-x: scroll; - display: block !important; - text-wrap: nowrap; + background-color: transparent; + margin: 0px 0px 20px; + overflow-x: scroll; + display: block !important; + text-wrap: nowrap; + border: none; } +.header-bar button.selected { + border: none; -.dark .header-bar { - border: none !important; - background-color: #8080802b !important; + /* an alternative header bar style with rounded background */ + /* background-color: var(--background-fill-primary); + border: 4px solid transparent; + border-radius: var(--radius-lg); + background-clip: padding-box; */ } -.header-bar button.selected { - border-radius: 0; +/* selected buttons have highlighted text */ +button.selected { + color: var(--block-label-text-color); + font-weight: bold; +} + +#chat-tab, +#settings-tab, +#help-tab, +#resources-tab, +#login-tab { + border: none !important; } -.indices-tab { - border: none !important; +#help-tab, +#settings-tab { + /* text-dense view should not be wide for readability */ + max-width: max(56vw, 900px) !important; + margin: 0 auto !important; } -#chat-tab, #settings-tab, #help-tab, #admin-tab, #login-tab { - border: none !important; +.indices-tab, +#resources-tab { + /* Other view should not be too wide */ + border: none !important; + max-width: max(70vw, 1200px) !important; + margin: 0 auto !important; } #main-chat-bot { - height: calc(100vh - 140px) !important; + /* span the chat area to occupy full height minus space for chat input */ + height: calc(100vh - 180px) !important; } #chat-info-panel { - max-height: calc(100vh - 140px) !important; - overflow-y: scroll !important; + max-height: calc(100vh - 180px) !important; + overflow-y: scroll !important; } .setting-answer-mode-description { - margin: 5px 5px 2px !important + margin: 5px 5px 2px !important; } - -mark { +*/ mark { background-color: #1496bb; } - /* clpse */ .clpse { background-color: var(--background-fill-secondary); @@ -61,3 +97,33 @@ mark { text-align: left; outline: none; } + +/* for setting transparent background for elements */ +.no-background { + background-color: transparent; + border: none; +} + +/* for setting bold text for elements */ +.bold-text { + font-weight: bold; +} + +/* for setting highlighted text for elements */ +.body-text-color { + color: var(--body-text-color); +} + +/* for setting right-aligned buttons */ +.right-button { + min-width: 200px !important; + width: fit-content; + padding-left: 20px; + padding-right: 20px; + margin: 0px 0px 0px auto; +} + +/* for setting height limit for buttons */ +.cap-height { + max-height: 42px; +} diff --git a/libs/ktem/ktem/index/file/ui.py b/libs/ktem/ktem/index/file/ui.py index 11d491f03..eb6ef3527 100644 --- a/libs/ktem/ktem/index/file/ui.py +++ b/libs/ktem/ktem/index/file/ui.py @@ -97,49 +97,66 @@ def upload_instruction(self) -> str: def on_building_ui(self): """Build the UI of the app""" - with gr.Accordion(label="File upload", open=True) as self.upload: - msg = self.upload_instruction() - if msg: - gr.Markdown(msg) - - self.files = File( - file_types=self._supported_file_types, - file_count="multiple", - container=False, - ) - with gr.Accordion("Advanced indexing options", open=False): - with gr.Row(): - self.reindex = gr.Checkbox( - value=False, label="Force reindex file", container=False + with gr.Row(): + with gr.Column(scale=1): + gr.Markdown("## File Upload") + with gr.Column() as self.upload: + msg = self.upload_instruction() + if msg: + gr.Markdown(msg) + + self.files = File( + file_types=self._supported_file_types, + file_count="multiple", + container=True, + ) + with gr.Accordion("Advanced indexing options", open=True): + with gr.Row(): + self.reindex = gr.Checkbox( + value=False, label="Force reindex file", container=False + ) + + self.upload_button = gr.Button( + "Upload and Index", variant="primary" + ) + self.file_output = gr.File( + visible=False, label="Output files (debug purpose)" ) - self.upload_button = gr.Button("Upload and Index") - self.file_output = gr.File( - visible=False, label="Output files (debug purpose)" - ) - - gr.Markdown("## File list") - self.file_list_state = gr.State(value=None) - self.file_list = gr.DataFrame( - headers=["id", "name", "size", "text_length", "date_created"], - interactive=False, - ) - - with gr.Row() as self.selection_info: - self.selected_file_id = gr.State(value=None) - self.selected_panel = gr.Markdown(self.selected_panel_false) - self.deselect_button = gr.Button("Deselect", visible=False) + with gr.Column(scale=4): + gr.Markdown("## File List") + self.file_list_state = gr.State(value=None) + self.file_list = gr.DataFrame( + headers=["id", "name", "size", "text_length", "date_created"], + interactive=False, + ) - with gr.Row() as self.tools: - with gr.Column(): - self.view_button = gr.Button("View Text (WIP)") - with gr.Column(): - self.delete_button = gr.Button("Delete") - with gr.Row(): - self.delete_yes = gr.Button( - "Confirm Delete", variant="primary", visible=False - ) - self.delete_no = gr.Button("Cancel", visible=False) + with gr.Row() as self.selection_info: + self.selected_file_id = gr.State(value=None) + with gr.Column(scale=2): + self.selected_panel = gr.Markdown(self.selected_panel_false) + with gr.Column(scale=1): + self.deselect_button = gr.Button( + "Deselect", + scale=1, + visible=False, + elem_classes=["right-button"], + ) + + self.delete_button = gr.Button( + "Delete", variant="stop", elem_classes=["right-button"] + ) + self.delete_yes = gr.Button( + "Confirm Delete", + variant="stop", + visible=False, + elem_classes=["right-button"], + ) + self.delete_no = gr.Button( + "Cancel", + visible=False, + elem_classes=["right-button"], + ) def on_subscribe_public_events(self): """Subscribe to the declared public event of the app""" diff --git a/libs/ktem/ktem/llms/ui.py b/libs/ktem/ktem/llms/ui.py index dd8f2bd2d..7644e27e2 100644 --- a/libs/ktem/ktem/llms/ui.py +++ b/libs/ktem/ktem/llms/ui.py @@ -52,13 +52,17 @@ def on_building_ui(self): with gr.Row(visible=False) as self._selected_panel_btn: with gr.Column(): - self.btn_edit_save = gr.Button("Save", min_width=10) + self.btn_edit_save = gr.Button( + "Save", min_width=10, variant="primary" + ) with gr.Column(): - self.btn_delete = gr.Button("Delete", min_width=10) + self.btn_delete = gr.Button( + "Delete", min_width=10, variant="stop" + ) with gr.Row(): self.btn_delete_yes = gr.Button( - "Confirm delete", - variant="primary", + "Confirm Delete", + variant="stop", visible=False, min_width=10, ) @@ -98,7 +102,7 @@ def on_building_ui(self): "by default across the application." ), ) - self.btn_new = gr.Button("Create LLM") + self.btn_new = gr.Button("Add LLM", variant="primary") with gr.Column(scale=3): self.spec_desc = gr.Markdown(self.spec_desc_default) diff --git a/libs/ktem/ktem/main.py b/libs/ktem/ktem/main.py index fa190b234..45ba47488 100644 --- a/libs/ktem/ktem/main.py +++ b/libs/ktem/ktem/main.py @@ -53,11 +53,11 @@ def ui(self): setattr(self, f"_index_{index.id}", page) with gr.Tab( - "Admin", - elem_id="admin-tab", - id="admin-tab", + "Resources", + elem_id="resources-tab", + id="resources-tab", visible=not self.f_user_management, - ) as self._tabs["admin-tab"]: + ) as self._tabs["resources-tab"]: self.admin_page = AdminPage(self) with gr.Tab( @@ -111,7 +111,7 @@ def signed_in_out(user_id): for k in self._tabs.keys(): if k == "login-tab": tabs_update.append(gr.update(visible=False)) - elif k == "admin-tab": + elif k == "resources-tab": tabs_update.append(gr.update(visible=is_admin)) else: tabs_update.append(gr.update(visible=True)) diff --git a/libs/ktem/ktem/pages/chat/__init__.py b/libs/ktem/ktem/pages/chat/__init__.py index 3f0157127..e31a49549 100644 --- a/libs/ktem/ktem/pages/chat/__init__.py +++ b/libs/ktem/ktem/pages/chat/__init__.py @@ -41,7 +41,7 @@ def on_building_ui(self): continue index_ui.unrender() # need to rerender later within Accordion - with gr.Accordion(label=f"{index.name} Index", open=False): + with gr.Accordion(label=f"{index.name} Index", open=True): index_ui.render() gr_index = index_ui.as_gradio_component() if gr_index: diff --git a/libs/ktem/ktem/pages/chat/chat_panel.py b/libs/ktem/ktem/pages/chat/chat_panel.py index 55b9258e9..deb4ede11 100644 --- a/libs/ktem/ktem/pages/chat/chat_panel.py +++ b/libs/ktem/ktem/pages/chat/chat_panel.py @@ -9,17 +9,33 @@ def __init__(self, app): def on_building_ui(self): self.chatbot = gr.Chatbot( + label="Kotaemon", + # placeholder="This is the beginning of a new conversation.", + show_label=True, elem_id="main-chat-bot", show_copy_button=True, likeable=True, - show_label=False, + bubble_full_width=False, ) with gr.Row(): self.text_input = gr.Text( - placeholder="Chat input", scale=15, container=False + placeholder="Chat input", + scale=15, + container=False, + ) + self.submit_btn = gr.Button( + value="Send", + scale=1, + min_width=10, + variant="primary", + elem_classes=["cap-height"], + ) + self.regen_btn = gr.Button( + value="Regen", + scale=1, + min_width=10, + elem_classes=["cap-height"], ) - self.submit_btn = gr.Button(value="Send", scale=1, min_width=10) - self.regen_btn = gr.Button(value="Regen", scale=1, min_width=10) def submit_msg(self, chat_input, chat_history): """Submit a message to the chatbot""" diff --git a/libs/ktem/ktem/pages/chat/control.py b/libs/ktem/ktem/pages/chat/control.py index f2ed99bb1..14d5ff4ca 100644 --- a/libs/ktem/ktem/pages/chat/control.py +++ b/libs/ktem/ktem/pages/chat/control.py @@ -29,47 +29,42 @@ def __init__(self, app): self.on_building_ui() def on_building_ui(self): - with gr.Accordion(label="Conversation control", open=True): - self.conversation_id = gr.State(value="") - self.conversation = gr.Dropdown( - label="Chat sessions", - choices=[], + gr.Markdown("## Conversations") + self.conversation_id = gr.State(value="") + self.conversation = gr.Dropdown( + label="Chat sessions", + choices=[], + container=False, + filterable=False, + interactive=True, + ) + + with gr.Row() as self._new_delete: + self.btn_new = gr.Button(value="New", min_width=10, variant="primary") + self.btn_del = gr.Button(value="Delete", min_width=10, variant="stop") + + with gr.Row(visible=False) as self._delete_confirm: + self.btn_del_conf = gr.Button( + value="Delete", + variant="stop", + min_width=10, + ) + self.btn_del_cnl = gr.Button(value="Cancel", min_width=10) + + with gr.Row(): + self.conversation_rn = gr.Text( + placeholder="Conversation name", container=False, - filterable=False, + scale=5, + min_width=10, interactive=True, ) - - 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( - placeholder="Conversation name", - container=False, - scale=5, - min_width=10, - interactive=True, - ) - self.conversation_rn_btn = gr.Button( - value="Rename", scale=1, min_width=10 - ) - - # current_state = gr.Text() - # show_current_state = gr.Button(value="Current") - # show_current_state.click( - # lambda a, b: "\n".join([a, b]), - # inputs=[cid, self.conversation], - # outputs=[current_state], - # ) + self.conversation_rn_btn = gr.Button( + value="Rename", + scale=1, + min_width=10, + elem_classes=["no-background", "body-text-color", "bold-text"], + ) def load_chat_history(self, user_id): """Reload chat history""" diff --git a/libs/ktem/ktem/pages/settings.py b/libs/ktem/ktem/pages/settings.py index 1c9f55fe5..23bbfac99 100644 --- a/libs/ktem/ktem/pages/settings.py +++ b/libs/ktem/ktem/pages/settings.py @@ -100,13 +100,18 @@ def __init__(self, app): self.on_building_ui() def on_building_ui(self): - self.setting_save_btn = gr.Button("Save settings") if self._app.f_user_management: - with gr.Tab("User settings"): + with gr.Tab("Users"): self.user_tab() - self.app_tab() - self.index_tab() - self.reasoning_tab() + with gr.Tab("General"): + self.app_tab() + with gr.Tab("Document Indices"): + self.index_tab() + with gr.Tab("Reasoning Pipelines"): + self.reasoning_tab() + self.setting_save_btn = gr.Button( + "Save changes", variant="primary", scale=1, elem_classes=["right-button"] + ) def on_subscribe_public_events(self): """ diff --git a/libs/ktem/launch.py b/libs/ktem/launch.py index 1f436c5b5..2ac7a1aac 100644 --- a/libs/ktem/launch.py +++ b/libs/ktem/launch.py @@ -2,4 +2,4 @@ app = App() demo = app.make() -demo.queue().launch(favicon_path=app._favicon) +demo.queue().launch(favicon_path=app._favicon, inbrowser=True) diff --git a/libs/ktem/pyproject.toml b/libs/ktem/pyproject.toml index 6fee8a043..172498f10 100644 --- a/libs/ktem/pyproject.toml +++ b/libs/ktem/pyproject.toml @@ -21,6 +21,7 @@ dependencies = [ "sqlalchemy", "sqlmodel", "tiktoken", + "gradio>=4.0.0,<=4.22.0", ] readme = "README.md" license = { text = "MIT License" }