diff --git a/flowsettings.py b/flowsettings.py
index 0962eefc..ae81be73 100644
--- a/flowsettings.py
+++ b/flowsettings.py
@@ -65,6 +65,8 @@ os.environ["HF_HUB_CACHE"] = str(KH_APP_DATA_DIR / "huggingface")
KH_DOC_DIR = this_dir / "docs"
KH_MODE = "dev"
+KH_SSO_ENABLED = config("KH_SSO_ENABLED", default=False, cast=bool)
+
KH_FEATURE_CHAT_SUGGESTION = config(
"KH_FEATURE_CHAT_SUGGESTION", default=False, cast=bool
)
@@ -145,7 +147,7 @@ if config("OPENAI_API_KEY", default=""):
"base_url": config("OPENAI_API_BASE", default="")
or "https://api.openai.com/v1",
"api_key": config("OPENAI_API_KEY", default=""),
- "model": config("OPENAI_CHAT_MODEL", default="gpt-3.5-turbo"),
+ "model": config("OPENAI_CHAT_MODEL", default="gpt-4o-mini"),
"timeout": 20,
},
"default": True,
@@ -156,7 +158,7 @@ if config("OPENAI_API_KEY", default=""):
"base_url": config("OPENAI_API_BASE", default="https://api.openai.com/v1"),
"api_key": config("OPENAI_API_KEY", default=""),
"model": config(
- "OPENAI_EMBEDDINGS_MODEL", default="text-embedding-ada-002"
+ "OPENAI_EMBEDDINGS_MODEL", default="text-embedding-3-large"
),
"timeout": 10,
"context_length": 8191,
@@ -323,7 +325,7 @@ GRAPHRAG_INDICES = [
".png, .jpeg, .jpg, .tiff, .tif, .pdf, .xls, .xlsx, .doc, .docx, "
".pptx, .csv, .html, .mhtml, .txt, .md, .zip"
),
- "private": False,
+ "private": True,
},
"index_type": graph_type,
}
@@ -338,7 +340,7 @@ KH_INDICES = [
".png, .jpeg, .jpg, .tiff, .tif, .pdf, .xls, .xlsx, .doc, .docx, "
".pptx, .csv, .html, .mhtml, .txt, .md, .zip"
),
- "private": False,
+ "private": True,
},
"index_type": "ktem.index.file.FileIndex",
},
diff --git a/libs/ktem/ktem/app.py b/libs/ktem/ktem/app.py
index c4dce356..48ee5a34 100644
--- a/libs/ktem/ktem/app.py
+++ b/libs/ktem/ktem/app.py
@@ -13,7 +13,7 @@ from ktem.settings import BaseSettingGroup, SettingGroup, SettingReasoningGroup
from theflow.settings import settings
from theflow.utils.modules import import_dotted_string
-BASE_PATH = os.environ.get("GRADIO_ROOT_PATH", "")
+BASE_PATH = os.environ.get("GR_FILE_ROOT_PATH", "")
class BaseApp:
@@ -57,7 +57,7 @@ class BaseApp:
self._pdf_view_js = self._pdf_view_js.replace(
"PDFJS_PREBUILT_DIR",
pdf_js_dist_dir,
- ).replace("GRADIO_ROOT_PATH", BASE_PATH)
+ ).replace("GR_FILE_ROOT_PATH", BASE_PATH)
with (dir_assets / "js" / "svg-pan-zoom.min.js").open() as fi:
self._svg_js = fi.read()
@@ -79,7 +79,7 @@ class BaseApp:
self.default_settings.index.finalize()
self.settings_state = gr.State(self.default_settings.flatten())
- self.user_id = gr.State(1 if not self.f_user_management else None)
+ self.user_id = gr.State(None)
def initialize_indices(self):
"""Create the index manager, start indices, and register to app settings"""
diff --git a/libs/ktem/ktem/assets/js/main.js b/libs/ktem/ktem/assets/js/main.js
index ad3c9911..d69e9a5f 100644
--- a/libs/ktem/ktem/assets/js/main.js
+++ b/libs/ktem/ktem/assets/js/main.js
@@ -11,6 +11,14 @@ function run() {
version_node.style = "position: fixed; top: 10px; right: 10px;";
main_parent.appendChild(version_node);
+ // add favicon
+ const favicon = document.createElement("link");
+ // set favicon attributes
+ favicon.rel = "icon";
+ favicon.type = "image/svg+xml";
+ favicon.href = "/favicon.svg";
+ document.head.appendChild(favicon);
+
// move info-expand-button
let info_expand_button = document.getElementById("info-expand-button");
let chat_info_panel = document.getElementById("info-expand");
diff --git a/libs/ktem/ktem/assets/js/pdf_viewer.js b/libs/ktem/ktem/assets/js/pdf_viewer.js
index 2166edbe..4b15f524 100644
--- a/libs/ktem/ktem/assets/js/pdf_viewer.js
+++ b/libs/ktem/ktem/assets/js/pdf_viewer.js
@@ -17,7 +17,7 @@ function onBlockLoad () {
⛶
diff --git a/libs/ktem/ktem/db/base_models.py b/libs/ktem/ktem/db/base_models.py
index 7c497056..ea0cbfc1 100644
--- a/libs/ktem/ktem/db/base_models.py
+++ b/libs/ktem/ktem/db/base_models.py
@@ -55,7 +55,7 @@ class BaseUser(SQLModel):
__table_args__ = {"extend_existing": True}
- id: Optional[int] = Field(default=None, primary_key=True)
+ id: Optional[str] = Field(default=None, primary_key=True)
username: str = Field(unique=True)
username_lower: str = Field(unique=True)
password: str
diff --git a/libs/ktem/ktem/main.py b/libs/ktem/ktem/main.py
index deeb415d..19dc7b3c 100644
--- a/libs/ktem/ktem/main.py
+++ b/libs/ktem/ktem/main.py
@@ -9,6 +9,7 @@ from ktem.pages.setup import SetupPage
from theflow.settings import settings as flowsettings
KH_DEMO_MODE = getattr(flowsettings, "KH_DEMO_MODE", False)
+KH_SSO_ENABLED = getattr(flowsettings, "KH_SSO_ENABLED", False)
KH_ENABLE_FIRST_SETUP = getattr(flowsettings, "KH_ENABLE_FIRST_SETUP", False)
KH_APP_DATA_EXISTS = getattr(flowsettings, "KH_APP_DATA_EXISTS", True)
@@ -90,14 +91,15 @@ class App(BaseApp):
page = index.get_index_page_ui()
setattr(self, f"_index_{index.id}", page)
- with gr.Tab(
- "Resources",
- elem_id="resources-tab",
- id="resources-tab",
- visible=not self.f_user_management,
- elem_classes=["fill-main-area-height", "scrollable"],
- ) as self._tabs["resources-tab"]:
- self.resources_page = ResourcesTab(self)
+ if not KH_SSO_ENABLED:
+ with gr.Tab(
+ "Resources",
+ elem_id="resources-tab",
+ id="resources-tab",
+ visible=not self.f_user_management,
+ elem_classes=["fill-main-area-height", "scrollable"],
+ ) as self._tabs["resources-tab"]:
+ self.resources_page = ResourcesTab(self)
with gr.Tab(
"Settings",
diff --git a/libs/ktem/ktem/pages/login.py b/libs/ktem/ktem/pages/login.py
index 9dc4839c..0a65c477 100644
--- a/libs/ktem/ktem/pages/login.py
+++ b/libs/ktem/ktem/pages/login.py
@@ -3,6 +3,7 @@ import hashlib
import gradio as gr
from ktem.app import BasePage
from ktem.db.models import User, engine
+from ktem.pages.resources.user import create_user
from sqlmodel import Session, select
fetch_creds = """
@@ -85,19 +86,44 @@ class LoginPage(BasePage):
},
)
- def login(self, usn, pwd):
- if not usn or not pwd:
- return None, usn, pwd
+ def login(self, usn, pwd, request: gr.Request):
+ import gradiologin as grlogin
+
+ user = grlogin.get_user(request)
+
+ if user:
+ user_id = user["sub"]
+ with Session(engine) as session:
+ stmt = select(User).where(
+ User.id == user_id,
+ )
+ result = session.exec(stmt).all()
- hashed_password = hashlib.sha256(pwd.encode()).hexdigest()
- with Session(engine) as session:
- stmt = select(User).where(
- User.username_lower == usn.lower().strip(),
- User.password == hashed_password,
- )
- result = session.exec(stmt).all()
if result:
- return result[0].id, "", ""
+ print("Existing user:", user)
+ return user_id, "", ""
+ else:
+ print("Creating new user:", user)
+ create_user(
+ usn=user["email"],
+ pwd="",
+ user_id=user_id,
+ is_admin=False,
+ )
+ return user_id, "", ""
+ else:
+ if not usn or not pwd:
+ return None, usn, pwd
- gr.Warning("Invalid username or password")
- 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().strip(),
+ User.password == hashed_password,
+ )
+ result = session.exec(stmt).all()
+ if result:
+ return result[0].id, "", ""
+
+ gr.Warning("Invalid username or password")
+ return None, usn, pwd
diff --git a/libs/ktem/ktem/pages/resources/user.py b/libs/ktem/ktem/pages/resources/user.py
index 106c2681..5753395f 100644
--- a/libs/ktem/ktem/pages/resources/user.py
+++ b/libs/ktem/ktem/pages/resources/user.py
@@ -94,7 +94,7 @@ def validate_password(pwd, pwd_cnf):
return ""
-def create_user(usn, pwd) -> bool:
+def create_user(usn, pwd, user_id=None, is_admin=True) -> bool:
with Session(engine) as session:
statement = select(User).where(User.username_lower == usn.lower())
result = session.exec(statement).all()
@@ -105,10 +105,11 @@ def create_user(usn, pwd) -> bool:
else:
hashed_password = hashlib.sha256(pwd.encode()).hexdigest()
user = User(
+ id=user_id,
username=usn,
username_lower=usn.lower(),
password=hashed_password,
- admin=True,
+ admin=is_admin,
)
session.add(user)
session.commit()
diff --git a/libs/ktem/ktem/pages/settings.py b/libs/ktem/ktem/pages/settings.py
index f899a459..5163d9f2 100644
--- a/libs/ktem/ktem/pages/settings.py
+++ b/libs/ktem/ktem/pages/settings.py
@@ -5,6 +5,10 @@ from ktem.app import BasePage
from ktem.components import reasonings
from ktem.db.models import Settings, User, engine
from sqlmodel import Session, select
+from theflow.settings import settings as flowsettings
+
+KH_SSO_ENABLED = getattr(flowsettings, "KH_SSO_ENABLED", False)
+
signout_js = """
function(u, c, pw, pwc) {
@@ -80,38 +84,44 @@ class SettingsPage(BasePage):
# render application page if there are application settings
self._render_app_tab = False
- if self._default_settings.application.settings:
+
+ if not KH_SSO_ENABLED and self._default_settings.application.settings:
self._render_app_tab = True
# render index page if there are index settings (general and/or specific)
self._render_index_tab = False
- if self._default_settings.index.settings:
- self._render_index_tab = True
- else:
- for sig in self._default_settings.index.options.values():
- if sig.settings:
- self._render_index_tab = True
- break
+
+ if not KH_SSO_ENABLED:
+ if self._default_settings.index.settings:
+ self._render_index_tab = True
+ else:
+ for sig in self._default_settings.index.options.values():
+ if sig.settings:
+ self._render_index_tab = True
+ break
# render reasoning page if there are reasoning settings
self._render_reasoning_tab = False
- if len(self._default_settings.reasoning.settings) > 1:
- self._render_reasoning_tab = True
- else:
- for sig in self._default_settings.reasoning.options.values():
- if sig.settings:
- self._render_reasoning_tab = True
- break
+
+ if not KH_SSO_ENABLED:
+ if len(self._default_settings.reasoning.settings) > 1:
+ self._render_reasoning_tab = True
+ else:
+ for sig in self._default_settings.reasoning.options.values():
+ if sig.settings:
+ self._render_reasoning_tab = True
+ break
self.on_building_ui()
def on_building_ui(self):
- self.setting_save_btn = gr.Button(
- "Save & Close",
- variant="primary",
- elem_classes=["right-button"],
- elem_id="save-setting-btn",
- )
+ if not KH_SSO_ENABLED:
+ self.setting_save_btn = gr.Button(
+ "Save & Close",
+ variant="primary",
+ elem_classes=["right-button"],
+ elem_id="save-setting-btn",
+ )
if self._app.f_user_management:
with gr.Tab("User settings"):
self.user_tab()
@@ -175,21 +185,22 @@ class SettingsPage(BasePage):
)
def on_register_events(self):
- self.setting_save_btn.click(
- self.save_setting,
- inputs=[self._user_id] + self.components(),
- outputs=self._settings_state,
- ).then(
- lambda: gr.Tabs(selected="chat-tab"),
- outputs=self._app.tabs,
- )
+ if not KH_SSO_ENABLED:
+ self.setting_save_btn.click(
+ self.save_setting,
+ inputs=[self._user_id] + self.components(),
+ outputs=self._settings_state,
+ ).then(
+ lambda: gr.Tabs(selected="chat-tab"),
+ outputs=self._app.tabs,
+ )
self._components["reasoning.use"].change(
self.change_reasoning_mode,
inputs=[self._components["reasoning.use"]],
outputs=list(self._reasoning_mode.values()),
show_progress="hidden",
)
- if self._app.f_user_management:
+ if self._app.f_user_management and not KH_SSO_ENABLED:
self.password_change_btn.click(
self.change_password,
inputs=[
@@ -223,15 +234,21 @@ class SettingsPage(BasePage):
def user_tab(self):
# user management
self.current_name = gr.Markdown("Current user: ___")
- self.signout = gr.Button("Logout")
- self.password_change = gr.Textbox(
- label="New password", interactive=True, type="password"
- )
- self.password_change_confirm = gr.Textbox(
- label="Confirm password", interactive=True, type="password"
- )
- self.password_change_btn = gr.Button("Change password", interactive=True)
+ if KH_SSO_ENABLED:
+ import gradiologin as grlogin
+
+ self.sso_singout = grlogin.LogoutButton("Logout")
+ else:
+ self.signout = gr.Button("Logout")
+
+ self.password_change = gr.Textbox(
+ label="New password", interactive=True, type="password"
+ )
+ self.password_change_confirm = gr.Textbox(
+ label="Confirm password", interactive=True, type="password"
+ )
+ self.password_change_btn = gr.Button("Change password", interactive=True)
def change_password(self, user_id, password, password_confirm):
from ktem.pages.resources.user import validate_password
diff --git a/libs/ktem/ktem/utils/render.py b/libs/ktem/ktem/utils/render.py
index 49c2f79f..aee25b16 100644
--- a/libs/ktem/ktem/utils/render.py
+++ b/libs/ktem/ktem/utils/render.py
@@ -5,7 +5,7 @@ from fast_langdetect import detect
from kotaemon.base import RetrievedDocument
-BASE_PATH = os.environ.get("GRADIO_ROOT_PATH", "")
+BASE_PATH = os.environ.get("GR_FILE_ROOT_PATH", "")
def is_close(val1, val2, tolerance=1e-9):
diff --git a/sso_app.py b/sso_app.py
new file mode 100644
index 00000000..270abe34
--- /dev/null
+++ b/sso_app.py
@@ -0,0 +1,51 @@
+import os
+
+import gradiologin as grlogin
+from decouple import config
+from fastapi import FastAPI
+from fastapi.responses import FileResponse
+from theflow.settings import settings as flowsettings
+
+KH_APP_DATA_DIR = getattr(flowsettings, "KH_APP_DATA_DIR", ".")
+GRADIO_TEMP_DIR = os.getenv("GRADIO_TEMP_DIR", None)
+# override GRADIO_TEMP_DIR if it's not set
+if GRADIO_TEMP_DIR is None:
+ GRADIO_TEMP_DIR = os.path.join(KH_APP_DATA_DIR, "gradio_tmp")
+ os.environ["GRADIO_TEMP_DIR"] = GRADIO_TEMP_DIR
+
+
+GOOGLE_CLIENT_ID = config("GOOGLE_CLIENT_ID", default="")
+GOOGLE_CLIENT_SECRET = config("GOOGLE_CLIENT_SECRET", default="")
+
+
+from ktem.main import App # noqa
+
+gradio_app = App()
+demo = gradio_app.make()
+
+app = FastAPI()
+grlogin.register(
+ name="google",
+ server_metadata_url="https://accounts.google.com/.well-known/openid-configuration",
+ client_id=GOOGLE_CLIENT_ID,
+ client_secret=GOOGLE_CLIENT_SECRET,
+ client_kwargs={
+ "scope": "openid email profile",
+ },
+)
+
+
+@app.get("/favicon.svg", include_in_schema=False)
+async def favicon():
+ return FileResponse(gradio_app._favicon)
+
+
+grlogin.mount_gradio_app(
+ app,
+ demo,
+ "/app",
+ allowed_paths=[
+ "libs/ktem/ktem/assets",
+ GRADIO_TEMP_DIR,
+ ],
+)