diff --git a/flowsettings.py b/flowsettings.py
index b8d4c3555..e248fac94 100644
--- a/flowsettings.py
+++ b/flowsettings.py
@@ -26,6 +26,7 @@
KH_ENABLE_FIRST_SETUP = True
KH_DEMO_MODE = config("KH_DEMO_MODE", default=False, cast=bool)
+KH_OLLAMA_URL = config("KH_OLLAMA_URL", default="http://localhost:11434/v1/")
# App can be ran from anywhere and it's not trivial to decide where to store app data.
# So let's use the same directory as the flowsetting.py file.
@@ -162,7 +163,7 @@
KH_LLMS["ollama"] = {
"spec": {
"__type__": "kotaemon.llms.ChatOpenAI",
- "base_url": "http://localhost:11434/v1/",
+ "base_url": KH_OLLAMA_URL,
"model": config("LOCAL_MODEL", default="llama3.1:8b"),
"api_key": "ollama",
},
@@ -171,7 +172,7 @@
KH_EMBEDDINGS["ollama"] = {
"spec": {
"__type__": "kotaemon.embeddings.OpenAIEmbeddings",
- "base_url": "http://localhost:11434/v1/",
+ "base_url": KH_OLLAMA_URL,
"model": config("LOCAL_MODEL_EMBEDDINGS", default="nomic-embed-text"),
"api_key": "ollama",
},
@@ -195,11 +196,11 @@
},
"default": False,
}
-KH_LLMS["gemini"] = {
+KH_LLMS["google"] = {
"spec": {
"__type__": "kotaemon.llms.chats.LCGeminiChat",
- "model_name": "gemini-1.5-pro",
- "api_key": "your-key",
+ "model_name": "gemini-1.5-flash",
+ "api_key": config("GOOGLE_API_KEY", default="your-key"),
},
"default": False,
}
@@ -231,6 +232,13 @@
},
"default": False,
}
+KH_EMBEDDINGS["google"] = {
+ "spec": {
+ "__type__": "kotaemon.embeddings.LCGoogleEmbeddings",
+ "model": "models/text-embedding-004",
+ "google_api_key": config("GOOGLE_API_KEY", default="your-key"),
+ }
+}
# KH_EMBEDDINGS["huggingface"] = {
# "spec": {
# "__type__": "kotaemon.embeddings.LCHuggingFaceEmbeddings",
@@ -303,7 +311,8 @@
GRAPHRAG_INDICES = [
{
- "name": graph_type.split(".")[-1].replace("Index", ""), # get last name
+ "name": graph_type.split(".")[-1].replace("Index", "")
+ + " Collection", # get last name
"config": {
"supported_file_types": (
".png, .jpeg, .jpg, .tiff, .tif, .pdf, .xls, .xlsx, .doc, .docx, "
@@ -318,7 +327,7 @@
KH_INDICES = [
{
- "name": "File",
+ "name": "File Collection",
"config": {
"supported_file_types": (
".png, .jpeg, .jpg, .tiff, .tif, .pdf, .xls, .xlsx, .doc, .docx, "
diff --git a/libs/kotaemon/kotaemon/embeddings/__init__.py b/libs/kotaemon/kotaemon/embeddings/__init__.py
index 92b3d1f4b..0ff777428 100644
--- a/libs/kotaemon/kotaemon/embeddings/__init__.py
+++ b/libs/kotaemon/kotaemon/embeddings/__init__.py
@@ -4,6 +4,7 @@
from .langchain_based import (
LCAzureOpenAIEmbeddings,
LCCohereEmbeddings,
+ LCGoogleEmbeddings,
LCHuggingFaceEmbeddings,
LCOpenAIEmbeddings,
)
@@ -18,6 +19,7 @@
"LCAzureOpenAIEmbeddings",
"LCCohereEmbeddings",
"LCHuggingFaceEmbeddings",
+ "LCGoogleEmbeddings",
"OpenAIEmbeddings",
"AzureOpenAIEmbeddings",
"FastEmbedEmbeddings",
diff --git a/libs/kotaemon/kotaemon/embeddings/langchain_based.py b/libs/kotaemon/kotaemon/embeddings/langchain_based.py
index 03ff9c670..9e8422a04 100644
--- a/libs/kotaemon/kotaemon/embeddings/langchain_based.py
+++ b/libs/kotaemon/kotaemon/embeddings/langchain_based.py
@@ -219,3 +219,38 @@ def _get_lc_class(self):
from langchain.embeddings import HuggingFaceBgeEmbeddings
return HuggingFaceBgeEmbeddings
+
+
+class LCGoogleEmbeddings(LCEmbeddingMixin, BaseEmbeddings):
+ """Wrapper around Langchain's Google GenAI embedding, focusing on key parameters"""
+
+ google_api_key: str = Param(
+ help="API key (https://aistudio.google.com/app/apikey)",
+ default=None,
+ required=True,
+ )
+ model: str = Param(
+ help="Model name to use (https://ai.google.dev/gemini-api/docs/models/gemini#text-embedding-and-embedding)", # noqa
+ default="models/text-embedding-004",
+ required=True,
+ )
+
+ def __init__(
+ self,
+ model: str = "models/text-embedding-004",
+ google_api_key: Optional[str] = None,
+ **params,
+ ):
+ super().__init__(
+ model=model,
+ google_api_key=google_api_key,
+ **params,
+ )
+
+ def _get_lc_class(self):
+ try:
+ from langchain_google_genai import GoogleGenerativeAIEmbeddings
+ except ImportError:
+ raise ImportError("Please install langchain-google-genai")
+
+ return GoogleGenerativeAIEmbeddings
diff --git a/libs/ktem/ktem/assets/css/main.css b/libs/ktem/ktem/assets/css/main.css
index dba11efe9..6c2e87cf4 100644
--- a/libs/ktem/ktem/assets/css/main.css
+++ b/libs/ktem/ktem/assets/css/main.css
@@ -97,7 +97,7 @@ button.selected {
#chat-info-panel {
max-height: var(--main-area-height) !important;
overflow: auto !important;
- transition: all 0.5s;
+ transition: all 0.4s;
}
body.dark #chat-info-panel figure>img{
@@ -109,12 +109,12 @@ body.dark #chat-info-panel figure>img{
flex-wrap: unset;
overflow-y: scroll !important;
position: sticky;
- min-width: min(305px, 100%) !important;
column-gap: 2px !important;
scrollbar-width: none;
/* Firefox */
-ms-overflow-style: none;
/* Internet Explorer 10+ */
+ transition: all 0.3s;
}
#conv-settings-panel::-webkit-scrollbar {
@@ -204,6 +204,13 @@ mark {
right: 15px;
}
+#chat-expand-button {
+ position: absolute;
+ top: 6px;
+ right: -10px;
+ z-index: 10;
+}
+
#use-mindmap-checkbox {
position: absolute;
width: 110px;
diff --git a/libs/ktem/ktem/assets/icons/expand.svg b/libs/ktem/ktem/assets/icons/expand.svg
new file mode 100644
index 000000000..36e87b0e8
--- /dev/null
+++ b/libs/ktem/ktem/assets/icons/expand.svg
@@ -0,0 +1 @@
+
diff --git a/libs/ktem/ktem/assets/js/main.js b/libs/ktem/ktem/assets/js/main.js
index 30c406717..ec6ea530c 100644
--- a/libs/ktem/ktem/assets/js/main.js
+++ b/libs/ktem/ktem/assets/js/main.js
@@ -16,6 +16,29 @@ function run() {
let chat_info_panel = document.getElementById("info-expand");
chat_info_panel.insertBefore(info_expand_button, chat_info_panel.childNodes[2]);
+ // move toggle-side-bar button
+ let chat_expand_button = document.getElementById("chat-expand-button");
+ let chat_column = document.getElementById("main-chat-bot");
+ let conv_column = document.getElementById("conv-settings-panel");
+
+ let default_conv_column_min_width = "min(300px, 100%)";
+ conv_column.style.minWidth = default_conv_column_min_width
+
+ globalThis.toggleChatColumn = (() => {
+ /* get flex-grow value of chat_column */
+ let flex_grow = conv_column.style.flexGrow;
+ console.log("chat col", flex_grow);
+ if (flex_grow == '0') {
+ conv_column.style.flexGrow = '1';
+ conv_column.style.minWidth = default_conv_column_min_width;
+ } else {
+ conv_column.style.flexGrow = '0';
+ conv_column.style.minWidth = "0px";
+ }
+ });
+
+ chat_column.insertBefore(chat_expand_button, chat_column.firstChild);
+
// move use mind-map checkbox
let mindmap_checkbox = document.getElementById("use-mindmap-checkbox");
let chat_setting_panel = document.getElementById("chat-settings-expand");
diff --git a/libs/ktem/ktem/embeddings/manager.py b/libs/ktem/ktem/embeddings/manager.py
index c33d151db..1c1c47027 100644
--- a/libs/ktem/ktem/embeddings/manager.py
+++ b/libs/ktem/ktem/embeddings/manager.py
@@ -57,6 +57,7 @@ def load_vendors(self):
AzureOpenAIEmbeddings,
FastEmbedEmbeddings,
LCCohereEmbeddings,
+ LCGoogleEmbeddings,
LCHuggingFaceEmbeddings,
OpenAIEmbeddings,
TeiEndpointEmbeddings,
@@ -68,6 +69,7 @@ def load_vendors(self):
FastEmbedEmbeddings,
LCCohereEmbeddings,
LCHuggingFaceEmbeddings,
+ LCGoogleEmbeddings,
TeiEndpointEmbeddings,
]
diff --git a/libs/ktem/ktem/index/file/base.py b/libs/ktem/ktem/index/file/base.py
index 427a3965f..d57943ba9 100644
--- a/libs/ktem/ktem/index/file/base.py
+++ b/libs/ktem/ktem/index/file/base.py
@@ -55,6 +55,8 @@ class BaseFileIndexIndexing(BaseComponent):
FSPath = Param(help="The file storage path")
user_id = Param(help="The user id")
private = Param(False, help="Whether this is private index")
+ chunk_size = Param(help="Chunk size for this index")
+ chunk_overlap = Param(help="Chunk overlap for this index")
def run(
self, file_paths: str | Path | list[str | Path], *args, **kwargs
diff --git a/libs/ktem/ktem/index/file/index.py b/libs/ktem/ktem/index/file/index.py
index f202a6a8e..9092d488a 100644
--- a/libs/ktem/ktem/index/file/index.py
+++ b/libs/ktem/ktem/index/file/index.py
@@ -404,6 +404,25 @@ def get_admin_settings(cls):
"choices": [("Yes", True), ("No", False)],
"info": "If private, files will not be accessible across users.",
},
+ "chunk_size": {
+ "name": "Size of chunk (number of tokens)",
+ "value": 0,
+ "component": "number",
+ "info": (
+ "Number of tokens of each text segment. "
+ "Set 0 to use developer setting."
+ ),
+ },
+ "chunk_overlap": {
+ "name": "Number of overlapping tokens between chunks",
+ "value": 0,
+ "component": "number",
+ "info": (
+ "Number of tokens that consecutive text segments "
+ "should overlap with each other. "
+ "Set 0 to use developer setting."
+ ),
+ },
}
def get_indexing_pipeline(self, settings, user_id) -> BaseFileIndexIndexing:
@@ -423,6 +442,8 @@ def get_indexing_pipeline(self, settings, user_id) -> BaseFileIndexIndexing:
obj.FSPath = self._fs_path
obj.user_id = user_id
obj.private = self.config.get("private", False)
+ obj.chunk_size = self.config.get("chunk_size", 0)
+ obj.chunk_overlap = self.config.get("chunk_overlap", 0)
return obj
diff --git a/libs/ktem/ktem/index/file/pipelines.py b/libs/ktem/ktem/index/file/pipelines.py
index 6b0033cc9..4d53e6538 100644
--- a/libs/ktem/ktem/index/file/pipelines.py
+++ b/libs/ktem/ktem/index/file/pipelines.py
@@ -729,7 +729,11 @@ def route(self, file_path: str | Path) -> IndexPipeline:
Can subclass this method for a more elaborate pipeline routing strategy.
"""
- _, chunk_size, chunk_overlap = dev_settings()
+
+ _, dev_chunk_size, dev_chunk_overlap = dev_settings()
+
+ chunk_size = self.chunk_size or dev_chunk_size
+ chunk_overlap = self.chunk_overlap or dev_chunk_overlap
# check if file_path is a URL
if self.is_url(file_path):
@@ -744,12 +748,14 @@ def route(self, file_path: str | Path) -> IndexPipeline:
"the suitable pipeline for this file type in the settings."
)
+ print(f"Chunk size: {chunk_size}, chunk overlap: {chunk_overlap}")
+
print("Using reader", reader)
pipeline: IndexPipeline = IndexPipeline(
loader=reader,
splitter=TokenSplitter(
chunk_size=chunk_size or 1024,
- chunk_overlap=chunk_overlap if chunk_overlap is not None else 256,
+ chunk_overlap=chunk_overlap or 256,
separator="\n\n",
backup_separators=["\n", ".", "\u200B"],
),
diff --git a/libs/ktem/ktem/main.py b/libs/ktem/ktem/main.py
index 00d23f20a..deeb415df 100644
--- a/libs/ktem/ktem/main.py
+++ b/libs/ktem/ktem/main.py
@@ -84,7 +84,7 @@ def ui(self):
) as self._tabs["indices-tab"]:
for index in self.index_manager.indices:
with gr.Tab(
- f"{index.name} Collection",
+ index.name,
elem_id=f"{index.id}-tab",
) as self._tabs[f"{index.id}-tab"]:
page = index.get_index_page_ui()
diff --git a/libs/ktem/ktem/pages/chat/__init__.py b/libs/ktem/ktem/pages/chat/__init__.py
index 045358735..8e3dbc021 100644
--- a/libs/ktem/ktem/pages/chat/__init__.py
+++ b/libs/ktem/ktem/pages/chat/__init__.py
@@ -8,7 +8,7 @@
from ktem.app import BasePage
from ktem.components import reasonings
from ktem.db.models import Conversation, engine
-from ktem.index.file.ui import File, chat_input_focus_js
+from ktem.index.file.ui import File
from ktem.reasoning.prompt_optimization.suggest_conversation_name import (
SuggestConvNamePipeline,
)
@@ -31,6 +31,12 @@
DEFAULT_SETTING = "(default)"
INFO_PANEL_SCALES = {True: 8, False: 4}
+chat_input_focus_js = """
+function() {
+ let chatInput = document.querySelector("#chat-input textarea");
+ chatInput.focus();
+}
+"""
pdfview_js = """
function() {
@@ -126,9 +132,7 @@ def on_building_ui(self):
continue
index_ui.unrender() # need to rerender later within Accordion
- with gr.Accordion(
- label=f"{index.name} Collection", open=index_id < 1
- ):
+ with gr.Accordion(label=index.name, open=index_id < 1):
index_ui.render()
gr_index = index_ui.as_gradio_component()
@@ -403,6 +407,9 @@ def on_register_events(self):
inputs=self._info_panel_expanded,
outputs=[self.info_column, self._info_panel_expanded],
)
+ self.chat_control.btn_chat_expand.click(
+ fn=None, inputs=None, js="function() {toggleChatColumn();}"
+ )
self.chat_panel.chatbot.like(
fn=self.is_liked,
diff --git a/libs/ktem/ktem/pages/chat/control.py b/libs/ktem/ktem/pages/chat/control.py
index db48cd643..ec11ef26a 100644
--- a/libs/ktem/ktem/pages/chat/control.py
+++ b/libs/ktem/ktem/pages/chat/control.py
@@ -48,9 +48,17 @@ def on_building_ui(self):
elem_classes=["no-background", "body-text-color"],
elem_id="toggle-dark-button",
)
+ self.btn_chat_expand = gr.Button(
+ value="",
+ icon=f"{ASSETS_DIR}/expand.svg",
+ scale=1,
+ size="sm",
+ elem_classes=["no-background", "body-text-color"],
+ elem_id="chat-expand-button",
+ )
self.btn_info_expand = gr.Button(
value="",
- icon=f"{ASSETS_DIR}/sidebar.svg",
+ icon=f"{ASSETS_DIR}/expand.svg",
min_width=2,
scale=1,
size="sm",
diff --git a/libs/ktem/ktem/pages/settings.py b/libs/ktem/ktem/pages/settings.py
index f60d86683..b74d641f0 100644
--- a/libs/ktem/ktem/pages/settings.py
+++ b/libs/ktem/ktem/pages/settings.py
@@ -272,7 +272,7 @@ def index_tab(self):
id2name = {k: v.name for k, v in self._app.index_manager.info().items()}
with gr.Tab("Retrieval settings", visible=self._render_index_tab):
for pn, sig in self._default_settings.index.options.items():
- name = "{} Collection".format(id2name.get(pn, f""))
+ name = id2name.get(pn, f"")
with gr.Tab(name):
for n, si in sig.settings.items():
obj = render_setting_item(si, si.value)
diff --git a/libs/ktem/ktem/pages/setup.py b/libs/ktem/ktem/pages/setup.py
index f7e70a118..21efa5d9a 100644
--- a/libs/ktem/ktem/pages/setup.py
+++ b/libs/ktem/ktem/pages/setup.py
@@ -9,7 +9,10 @@
from theflow.settings import settings as flowsettings
KH_DEMO_MODE = getattr(flowsettings, "KH_DEMO_MODE", False)
-DEFAULT_OLLAMA_URL = "http://localhost:11434/api"
+KH_OLLAMA_URL = getattr(flowsettings, "KH_OLLAMA_URL", "http://localhost:11434/v1/")
+DEFAULT_OLLAMA_URL = KH_OLLAMA_URL.replace("v1", "api")
+if DEFAULT_OLLAMA_URL.endswith("/"):
+ DEFAULT_OLLAMA_URL = DEFAULT_OLLAMA_URL[:-1]
DEMO_MESSAGE = (
@@ -55,8 +58,9 @@ def on_building_ui(self):
gr.Markdown(f"# Welcome to {self._app.app_name} first setup!")
self.radio_model = gr.Radio(
[
- ("Cohere API (*free registration* available) - recommended", "cohere"),
- ("OpenAI API (for more advance models)", "openai"),
+ ("Cohere API (*free registration*) - recommended", "cohere"),
+ ("Google API (*free registration*)", "google"),
+ ("OpenAI API (for GPT-based models)", "openai"),
("Local LLM (for completely *private RAG*)", "ollama"),
],
label="Select your model provider",
@@ -92,6 +96,18 @@ def on_building_ui(self):
show_label=False, placeholder="Cohere API Key"
)
+ with gr.Column(visible=False) as self.google_option:
+ gr.Markdown(
+ (
+ "#### Google API Key\n\n"
+ "(register your free API key "
+ "at https://aistudio.google.com/app/apikey)"
+ )
+ )
+ self.google_api_key = gr.Textbox(
+ show_label=False, placeholder="Google API Key"
+ )
+
with gr.Column(visible=False) as self.ollama_option:
gr.Markdown(
(
@@ -119,7 +135,12 @@ def on_register_events(self):
self.openai_api_key.submit,
],
fn=self.update_model,
- inputs=[self.cohere_api_key, self.openai_api_key, self.radio_model],
+ inputs=[
+ self.cohere_api_key,
+ self.openai_api_key,
+ self.google_api_key,
+ self.radio_model,
+ ],
outputs=[self.setup_log],
show_progress="hidden",
)
@@ -147,13 +168,19 @@ def on_register_events(self):
fn=self.switch_options_view,
inputs=[self.radio_model],
show_progress="hidden",
- outputs=[self.cohere_option, self.openai_option, self.ollama_option],
+ outputs=[
+ self.cohere_option,
+ self.openai_option,
+ self.ollama_option,
+ self.google_option,
+ ],
)
def update_model(
self,
cohere_api_key,
openai_api_key,
+ google_api_key,
radio_model_value,
):
# skip if KH_DEMO_MODE
@@ -221,12 +248,32 @@ def update_model(
},
default=True,
)
+ elif radio_model_value == "google":
+ if google_api_key:
+ llms.update(
+ name="google",
+ spec={
+ "__type__": "kotaemon.llms.chats.LCGeminiChat",
+ "model_name": "gemini-1.5-flash",
+ "api_key": google_api_key,
+ },
+ default=True,
+ )
+ embeddings.update(
+ name="google",
+ spec={
+ "__type__": "kotaemon.embeddings.LCGoogleEmbeddings",
+ "model": "models/text-embedding-004",
+ "google_api_key": google_api_key,
+ },
+ default=True,
+ )
elif radio_model_value == "ollama":
llms.update(
name="ollama",
spec={
"__type__": "kotaemon.llms.ChatOpenAI",
- "base_url": "http://localhost:11434/v1/",
+ "base_url": KH_OLLAMA_URL,
"model": "llama3.1:8b",
"api_key": "ollama",
},
@@ -236,7 +283,7 @@ def update_model(
name="ollama",
spec={
"__type__": "kotaemon.embeddings.OpenAIEmbeddings",
- "base_url": "http://localhost:11434/v1/",
+ "base_url": KH_OLLAMA_URL,
"model": "nomic-embed-text",
"api_key": "ollama",
},
@@ -270,7 +317,7 @@ def update_model(
yield log_content
except Exception as e:
log_content += (
- "Make sure you have download and installed Ollama correctly."
+ "Make sure you have download and installed Ollama correctly. "
f"Got error: {str(e)}"
)
yield log_content
@@ -345,9 +392,9 @@ def update_default_settings(self, radio_model_value, default_settings):
return default_settings
def switch_options_view(self, radio_model_value):
- components_visible = [gr.update(visible=False) for _ in range(3)]
+ components_visible = [gr.update(visible=False) for _ in range(4)]
- values = ["cohere", "openai", "ollama", None]
+ values = ["cohere", "openai", "ollama", "google", None]
assert radio_model_value in values, f"Invalid value {radio_model_value}"
if radio_model_value is not None: