diff --git a/src/khoj/interface/web/assets/khoj.css b/src/khoj/interface/web/assets/khoj.css
index 8ec85b10..1e2274a2 100644
--- a/src/khoj/interface/web/assets/khoj.css
+++ b/src/khoj/interface/web/assets/khoj.css
@@ -48,7 +48,7 @@
}
.khoj-nav {
display: grid;
- grid-template-columns: repeat(3, 1fr);
+ grid-auto-flow: column;
grid-gap: 32px;
justify-self: center;
}
diff --git a/src/khoj/interface/web/base_config.html b/src/khoj/interface/web/base_config.html
index 7e6bf09c..d15f63e4 100644
--- a/src/khoj/interface/web/base_config.html
+++ b/src/khoj/interface/web/base_config.html
@@ -2,6 +2,7 @@
+
Khoj - Settings
diff --git a/src/khoj/interface/web/chat.html b/src/khoj/interface/web/chat.html
index 56cc99d8..1ba00b20 100644
--- a/src/khoj/interface/web/chat.html
+++ b/src/khoj/interface/web/chat.html
@@ -113,13 +113,31 @@
}
-
+
@@ -296,5 +314,30 @@
grid-column: 2;
}
}
+
+ div.khoj-banner-container {
+ background: linear-gradient(-45deg, #FFC107, #FF9800, #FF5722, #FF9800, #FFC107);
+ background-size: 400% 400%;
+ animation: gradient 15s ease infinite;
+ text-align: center;
+ padding: 10px;
+ }
+
+ @keyframes gradient {
+ 0% {
+ background-position: 0% 50%;
+ }
+ 50% {
+ background-position: 100% 50%;
+ }
+ 100% {
+ background-position: 0% 50%;
+ }
+ }
+
+ a.khoj-banner {
+ color: black;
+ text-decoration: none;
+ }
diff --git a/src/khoj/interface/web/index.html b/src/khoj/interface/web/index.html
index 49929b45..09e2e690 100644
--- a/src/khoj/interface/web/index.html
+++ b/src/khoj/interface/web/index.html
@@ -232,13 +232,36 @@
+ {% if demo %}
+
+
+ {% endif %}
+
@@ -411,13 +434,90 @@
border-radius: 5px;
padding: 10px;
margin: 10px 0;
- border: 1px solid rgb(229, 229, 229);
+ border: 4px solid rgb(229, 229, 229);
}
img {
max-width: 90%;
}
+ div.khoj-banner-container {
+ background: linear-gradient(-45deg, #FFC107, #FF9800, #FF5722, #FF9800, #FFC107);
+ background-size: 400% 400%;
+ animation: gradient 15s ease infinite;
+ text-align: center;
+ padding: 10px;
+ }
+
+ @keyframes gradient {
+ 0% {
+ background-position: 0% 50%;
+ }
+ 50% {
+ background-position: 100% 50%;
+ }
+ 100% {
+ background-position: 0% 50%;
+ }
+ }
+
+ a.khoj-banner {
+ color: black;
+ display: block;
+ }
+
+ a.khoj-logo {
+ text-align: center;
+ }
+
+ p.khoj-banner {
+ margin: 0;
+ padding: 10px;
+ }
+
+ button#khoj-banner-submit,
+ input#khoj-banner-email {
+ padding: 10px;
+ border-radius: 5px;
+ border: 1px solid #475569;
+ background: #f9fafc;
+ }
+
+ button#khoj-banner-submit:hover,
+ input#khoj-banner-email:hover {
+ box-shadow: 0 0 11px #aaa;
+ }
+
+
diff --git a/src/khoj/main.py b/src/khoj/main.py
index bfaec286..5c2c71c0 100644
--- a/src/khoj/main.py
+++ b/src/khoj/main.py
@@ -129,6 +129,7 @@ def set_state(args):
state.verbose = args.verbose
state.host = args.host
state.port = args.port
+ state.demo = args.demo
def start_server(app, host=None, port=None, socket=None):
diff --git a/src/khoj/processor/github/github_to_jsonl.py b/src/khoj/processor/github/github_to_jsonl.py
index 6f21749d..7e0de284 100644
--- a/src/khoj/processor/github/github_to_jsonl.py
+++ b/src/khoj/processor/github/github_to_jsonl.py
@@ -15,6 +15,7 @@ from khoj.processor.org_mode.org_to_jsonl import OrgToJsonl
from khoj.processor.text_to_jsonl import TextToJsonl
from khoj.utils.jsonl import dump_jsonl, compress_jsonl_data
from khoj.utils.rawconfig import Entry
+from khoj.utils import state
logger = logging.getLogger(__name__)
@@ -38,6 +39,10 @@ class GithubToJsonl(TextToJsonl):
return
def process(self, previous_entries=None):
+ # If demo mode is enabled, don't re-process any of the repositories. This is resource intensive.
+ if state.demo and previous_entries is not None:
+ return self.update_entries_with_ids(previous_entries, previous_entries)
+
current_entries = []
for repo in self.config.repos:
current_entries += self.process_repo(repo, previous_entries)
@@ -193,7 +198,7 @@ class GithubToJsonl(TextToJsonl):
def _get_issues(self, issues_url: Union[str, None]) -> List[Dict]:
issues = []
- per_page = 30
+ per_page = 100
params = {"per_page": per_page, "state": "all"}
while issues_url is not None:
@@ -225,7 +230,7 @@ class GithubToJsonl(TextToJsonl):
def get_comments(self, comments_url: Union[str, None]) -> List[Dict]:
# By default, the number of results per page is 30. We'll keep it as-is for now.
comments = []
- per_page = 30
+ per_page = 100
params = {"per_page": per_page}
while comments_url is not None:
diff --git a/src/khoj/routers/api.py b/src/khoj/routers/api.py
index 58ba713d..0f57324b 100644
--- a/src/khoj/routers/api.py
+++ b/src/khoj/routers/api.py
@@ -39,6 +39,66 @@ from khoj.utils.yaml import save_config_to_file_updated_state
api = APIRouter()
logger = logging.getLogger(__name__)
+if not state.demo:
+
+ @api.get("/config/data", response_model=FullConfig)
+ def get_config_data():
+ return state.config
+
+ @api.post("/config/data")
+ async def set_config_data(updated_config: FullConfig):
+ state.config = updated_config
+ with open(state.config_file, "w") as outfile:
+ yaml.dump(yaml.safe_load(state.config.json(by_alias=True)), outfile)
+ outfile.close()
+ return state.config
+
+ @api.post("/config/data/content_type/github", status_code=200)
+ async def set_content_config_github_data(updated_config: GithubContentConfig):
+ if not state.config:
+ state.config = FullConfig()
+ state.config.search_type = SearchConfig.parse_obj(constants.default_config["search-type"])
+
+ if not state.config.content_type:
+ state.config.content_type = ContentConfig(**{"github": updated_config})
+ else:
+ state.config.content_type.github = updated_config
+
+ try:
+ save_config_to_file_updated_state()
+ return {"status": "ok"}
+ except Exception as e:
+ return {"status": "error", "message": str(e)}
+
+ @api.post("/config/data/content_type/{content_type}", status_code=200)
+ async def set_content_config_data(content_type: str, updated_config: TextContentConfig):
+ if not state.config:
+ state.config = FullConfig()
+ state.config.search_type = SearchConfig.parse_obj(constants.default_config["search-type"])
+
+ if not state.config.content_type:
+ state.config.content_type = ContentConfig(**{content_type: updated_config})
+ else:
+ state.config.content_type[content_type] = updated_config
+
+ try:
+ save_config_to_file_updated_state()
+ return {"status": "ok"}
+ except Exception as e:
+ return {"status": "error", "message": str(e)}
+
+ @api.post("/config/data/processor/conversation", status_code=200)
+ async def set_processor_conversation_config_data(updated_config: ConversationProcessorConfig):
+ if not state.config:
+ state.config = FullConfig()
+ state.config.search_type = SearchConfig.parse_obj(constants.default_config["search-type"])
+ state.config.processor = ProcessorConfig(conversation=updated_config)
+ try:
+ save_config_to_file_updated_state()
+ return {"status": "ok"}
+ except Exception as e:
+ return {"status": "error", "message": str(e)}
+
# Create Routes
@api.get("/config/data/default")
@@ -68,69 +128,6 @@ def get_config_types():
]
-@api.get("/config/data", response_model=FullConfig)
-def get_config_data():
- return state.config
-
-
-@api.post("/config/data")
-async def set_config_data(updated_config: FullConfig):
- state.config = updated_config
- with open(state.config_file, "w") as outfile:
- yaml.dump(yaml.safe_load(state.config.json(by_alias=True)), outfile)
- outfile.close()
- return state.config
-
-
-@api.post("/config/data/content_type/github", status_code=200)
-async def set_content_config_github_data(updated_config: GithubContentConfig):
- if not state.config:
- state.config = FullConfig()
- state.config.search_type = SearchConfig.parse_obj(constants.default_config["search-type"])
-
- if not state.config.content_type:
- state.config.content_type = ContentConfig(**{"github": updated_config})
- else:
- state.config.content_type.github = updated_config
-
- try:
- save_config_to_file_updated_state()
- return {"status": "ok"}
- except Exception as e:
- return {"status": "error", "message": str(e)}
-
-
-@api.post("/config/data/content_type/{content_type}", status_code=200)
-async def set_content_config_data(content_type: str, updated_config: TextContentConfig):
- if not state.config:
- state.config = FullConfig()
- state.config.search_type = SearchConfig.parse_obj(constants.default_config["search-type"])
-
- if not state.config.content_type:
- state.config.content_type = ContentConfig(**{content_type: updated_config})
- else:
- state.config.content_type[content_type] = updated_config
-
- try:
- save_config_to_file_updated_state()
- return {"status": "ok"}
- except Exception as e:
- return {"status": "error", "message": str(e)}
-
-
-@api.post("/config/data/processor/conversation", status_code=200)
-async def set_processor_conversation_config_data(updated_config: ConversationProcessorConfig):
- if not state.config:
- state.config = FullConfig()
- state.config.search_type = SearchConfig.parse_obj(constants.default_config["search-type"])
- state.config.processor = ProcessorConfig(conversation=updated_config)
- try:
- save_config_to_file_updated_state()
- return {"status": "ok"}
- except Exception as e:
- return {"status": "error", "message": str(e)}
-
-
@api.get("/search", response_model=List[SearchResponse])
async def search(
q: str,
diff --git a/src/khoj/routers/web_client.py b/src/khoj/routers/web_client.py
index 1ca57579..6ab94181 100644
--- a/src/khoj/routers/web_client.py
+++ b/src/khoj/routers/web_client.py
@@ -21,95 +21,94 @@ VALID_CONTENT_TYPES = ["org", "ledger", "markdown", "pdf"]
# Create Routes
@web_client.get("/", response_class=FileResponse)
-def index():
- return FileResponse(constants.web_directory / "index.html")
-
-
-@web_client.get("/config", response_class=HTMLResponse)
-def config_page(request: Request):
- return templates.TemplateResponse("config.html", context={"request": request})
-
-
-@web_client.get("/config/content_type/github", response_class=HTMLResponse)
-def github_config_page(request: Request):
- default_copy = constants.default_config.copy()
- default_github = default_copy["content-type"]["github"] # type: ignore
-
- default_config = TextContentConfig(
- compressed_jsonl=default_github["compressed-jsonl"],
- embeddings_file=default_github["embeddings-file"],
- )
-
- current_config = (
- state.config.content_type.github
- if state.config and state.config.content_type and state.config.content_type.github
- else default_config
- )
-
- current_config = json.loads(current_config.json())
-
- return templates.TemplateResponse(
- "content_type_github_input.html", context={"request": request, "current_config": current_config}
- )
-
-
-@web_client.get("/config/content_type/{content_type}", response_class=HTMLResponse)
-def content_config_page(request: Request, content_type: str):
- if content_type not in VALID_CONTENT_TYPES:
- return templates.TemplateResponse("config.html", context={"request": request})
-
- default_copy = constants.default_config.copy()
- default_content_type = default_copy["content-type"][content_type] # type: ignore
-
- default_config = TextContentConfig(
- compressed_jsonl=default_content_type["compressed-jsonl"],
- embeddings_file=default_content_type["embeddings-file"],
- )
-
- current_config = (
- state.config.content_type[content_type]
- if state.config and state.config.content_type and state.config.content_type[content_type] # type: ignore
- else default_config
- )
- current_config = json.loads(current_config.json())
-
- return templates.TemplateResponse(
- "content_type_input.html",
- context={
- "request": request,
- "current_config": current_config,
- "content_type": content_type,
- },
- )
-
-
-@web_client.get("/config/processor/conversation", response_class=HTMLResponse)
-def conversation_processor_config_page(request: Request):
- default_copy = constants.default_config.copy()
- default_processor_config = default_copy["processor"]["conversation"] # type: ignore
- default_processor_config = ConversationProcessorConfig(
- openai_api_key="",
- model=default_processor_config["model"],
- conversation_logfile=default_processor_config["conversation-logfile"],
- chat_model=default_processor_config["chat-model"],
- )
-
- current_processor_conversation_config = (
- state.config.processor.conversation
- if state.config and state.config.processor and state.config.processor.conversation
- else default_processor_config
- )
- current_processor_conversation_config = json.loads(current_processor_conversation_config.json())
-
- return templates.TemplateResponse(
- "processor_conversation_input.html",
- context={
- "request": request,
- "current_config": current_processor_conversation_config,
- },
- )
+def index(request: Request):
+ return templates.TemplateResponse("index.html", context={"request": request, "demo": state.demo})
@web_client.get("/chat", response_class=FileResponse)
-def chat_page():
- return FileResponse(constants.web_directory / "chat.html")
+def chat_page(request: Request):
+ return templates.TemplateResponse("chat.html", context={"request": request, "demo": state.demo})
+
+
+if not state.demo:
+
+ @web_client.get("/config", response_class=HTMLResponse)
+ def config_page(request: Request):
+ return templates.TemplateResponse("config.html", context={"request": request})
+
+ @web_client.get("/config/content_type/github", response_class=HTMLResponse)
+ def github_config_page(request: Request):
+ default_copy = constants.default_config.copy()
+ default_github = default_copy["content-type"]["github"] # type: ignore
+
+ default_config = TextContentConfig(
+ compressed_jsonl=default_github["compressed-jsonl"],
+ embeddings_file=default_github["embeddings-file"],
+ )
+
+ current_config = (
+ state.config.content_type.github
+ if state.config and state.config.content_type and state.config.content_type.github
+ else default_config
+ )
+
+ current_config = json.loads(current_config.json())
+
+ return templates.TemplateResponse(
+ "content_type_github_input.html", context={"request": request, "current_config": current_config}
+ )
+
+ @web_client.get("/config/content_type/{content_type}", response_class=HTMLResponse)
+ def content_config_page(request: Request, content_type: str):
+ if content_type not in VALID_CONTENT_TYPES:
+ return templates.TemplateResponse("config.html", context={"request": request})
+
+ default_copy = constants.default_config.copy()
+ default_content_type = default_copy["content-type"][content_type] # type: ignore
+
+ default_config = TextContentConfig(
+ compressed_jsonl=default_content_type["compressed-jsonl"],
+ embeddings_file=default_content_type["embeddings-file"],
+ )
+
+ current_config = (
+ state.config.content_type[content_type]
+ if state.config and state.config.content_type and state.config.content_type[content_type] # type: ignore
+ else default_config
+ )
+ current_config = json.loads(current_config.json())
+
+ return templates.TemplateResponse(
+ "content_type_input.html",
+ context={
+ "request": request,
+ "current_config": current_config,
+ "content_type": content_type,
+ },
+ )
+
+ @web_client.get("/config/processor/conversation", response_class=HTMLResponse)
+ def conversation_processor_config_page(request: Request):
+ default_copy = constants.default_config.copy()
+ default_processor_config = default_copy["processor"]["conversation"] # type: ignore
+ default_processor_config = ConversationProcessorConfig(
+ openai_api_key="",
+ model=default_processor_config["model"],
+ conversation_logfile=default_processor_config["conversation-logfile"],
+ chat_model=default_processor_config["chat-model"],
+ )
+
+ current_processor_conversation_config = (
+ state.config.processor.conversation
+ if state.config and state.config.processor and state.config.processor.conversation
+ else default_processor_config
+ )
+ current_processor_conversation_config = json.loads(current_processor_conversation_config.json())
+
+ return templates.TemplateResponse(
+ "processor_conversation_input.html",
+ context={
+ "request": request,
+ "current_config": current_processor_conversation_config,
+ },
+ )
diff --git a/src/khoj/utils/cli.py b/src/khoj/utils/cli.py
index 37d65458..ba0ef971 100644
--- a/src/khoj/utils/cli.py
+++ b/src/khoj/utils/cli.py
@@ -34,6 +34,7 @@ def cli(args=None):
help="Path to UNIX socket for server. Use to run server behind reverse proxy. Default: /tmp/uvicorn.sock",
)
parser.add_argument("--version", "-V", action="store_true", help="Print the installed Khoj version and exit")
+ parser.add_argument("--demo", action="store_true", default=False, help="Run Khoj in demo mode")
args = parser.parse_args(args)
diff --git a/src/khoj/utils/state.py b/src/khoj/utils/state.py
index a3368084..d59239d8 100644
--- a/src/khoj/utils/state.py
+++ b/src/khoj/utils/state.py
@@ -27,6 +27,7 @@ search_index_lock = threading.Lock()
SearchType = utils_config.SearchType
telemetry: List[Dict[str, str]] = []
previous_query: str = None
+demo: bool = False
if torch.cuda.is_available():
# Use CUDA GPU