From d7dbb715ef574fe0aa6e4ea8f2b3f4d6a46a6840 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Tue, 13 Feb 2024 22:38:03 +0530 Subject: [PATCH 1/6] Fix docs links in khoj introductory chat message --- src/khoj/interface/web/chat.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/khoj/interface/web/chat.html b/src/khoj/interface/web/chat.html index eb5a00ab..841ebbde 100644 --- a/src/khoj/interface/web/chat.html +++ b/src/khoj/interface/web/chat.html @@ -16,11 +16,11 @@ Hi, I am Khoj, your open, personal AI 👋🏽. I can help: - 🧠 Answer general knowledge questions - 💡 Be a sounding board for your ideas - 📜 Chat with your notes & documents -- 🌄 Generate images based on your messages (start your prompt with "/image") -- 🔎 Search the web for answers to your questions (start your prompt with "/online") +- 🌄 Generate images based on your messages +- 🔎 Search the web for answers to your questions - 🎙️ Listen to your audio messages (use the mic by the input box to speak your message) -Get the Khoj [Desktop](https://khoj.dev/downloads), [Obsidian](https://docs.khoj.dev/#/obsidian?id=setup) or [Emacs](https://docs.khoj.dev/#/emacs?id=setup) app to search, chat with your 🖥️ computer docs. +Get the Khoj [Desktop](https://khoj.dev/downloads), [Obsidian](https://docs.khoj.dev/clients/obsidian#setup), [Emacs](https://docs.khoj.dev/clients/emacs#setup) apps to search, chat with your 🖥️ computer docs. To get started, just start typing below. You can also type / to see a list of commands. `.trim() From cf4a524988ba32908148791e5dc9643b0f30af8a Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Wed, 14 Feb 2024 15:20:27 +0530 Subject: [PATCH 2/6] Move production dependencies to prod python packages group This will reduce khoj dependencies to install for self-hosting users - Move auth production dependencies to prod python packages group - Only enable authentication API router if not in anonymous mode - Improve error with requirements to enable authentication when not in anonymous mode --- prod.Dockerfile | 2 +- pyproject.toml | 10 ++++++---- src/khoj/configure.py | 22 ++++++++++++++++------ src/khoj/routers/auth.py | 26 +++++++++++++++++--------- src/khoj/routers/subscription.py | 8 +++++--- src/khoj/routers/twilio.py | 4 ++-- 6 files changed, 47 insertions(+), 25 deletions(-) diff --git a/prod.Dockerfile b/prod.Dockerfile index 8b21cb66..2471d9b8 100644 --- a/prod.Dockerfile +++ b/prod.Dockerfile @@ -13,7 +13,7 @@ COPY pyproject.toml . COPY README.md . ARG VERSION=0.0.0 RUN sed -i "s/dynamic = \\[\"version\"\\]/version = \"$VERSION\"/" pyproject.toml && \ - TMPDIR=/home/cache/ pip install --cache-dir=/home/cache/ -e . + TMPDIR=/home/cache/ pip install --cache-dir=/home/cache/ -e .[prod] # Copy Source Code COPY . . diff --git a/pyproject.toml b/pyproject.toml index cc4bdea9..aa52e06c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -69,17 +69,13 @@ dependencies = [ "httpx == 0.25.0", "pgvector == 0.2.4", "psycopg2-binary == 2.9.9", - "google-auth == 2.23.3", - "python-multipart == 0.0.6", "gunicorn == 21.2.0", "lxml == 4.9.3", "tzdata == 2023.3", "rapidocr-onnxruntime == 1.3.8", - "stripe == 7.3.0", "openai-whisper >= 20231117", "django-phonenumber-field == 7.3.0", "phonenumbers == 8.13.27", - "twilio == 8.11" ] dynamic = ["version"] @@ -93,6 +89,11 @@ Releases = "https://github.com/khoj-ai/khoj/releases" khoj = "khoj.main:run" [project.optional-dependencies] +prod = [ + "google-auth == 2.23.3", + "stripe == 7.3.0", + "twilio == 8.11", +] test = [ "pytest >= 7.1.2", "freezegun >= 1.2.0", @@ -103,6 +104,7 @@ test = [ ] dev = [ "khoj-assistant[test]", + "khoj-assistant[prod]", "mypy >= 1.0.1", "black >= 23.1.0", "pre-commit >= 3.0.4", diff --git a/src/khoj/configure.py b/src/khoj/configure.py index 4fdf6f99..95b32018 100644 --- a/src/khoj/configure.py +++ b/src/khoj/configure.py @@ -73,6 +73,7 @@ class UserAuthenticationBackend(AuthenticationBackend): Subscription.objects.create(user=default_user, type="standard", renewal_date=renewal_date) async def authenticate(self, request: HTTPConnection): + # Request from Web client current_user = request.session.get("user") if current_user and current_user.get("email"): user = ( @@ -93,6 +94,8 @@ class UserAuthenticationBackend(AuthenticationBackend): if subscribed: return AuthCredentials(["authenticated", "premium"]), AuthenticatedKhojUser(user) return AuthCredentials(["authenticated"]), AuthenticatedKhojUser(user) + + # Request from Desktop, Emacs, Obsidian clients if len(request.headers.get("Authorization", "").split("Bearer ")) == 2: # Get bearer token from header bearer_token = request.headers["Authorization"].split("Bearer ")[1] @@ -116,7 +119,8 @@ class UserAuthenticationBackend(AuthenticationBackend): if subscribed: return AuthCredentials(["authenticated", "premium"]), AuthenticatedKhojUser(user_with_token.user) return AuthCredentials(["authenticated"]), AuthenticatedKhojUser(user_with_token.user) - # Get query params for client_id and client_secret + + # Request from Whatsapp client client_id = request.query_params.get("client_id") if client_id: # Get the client secret, which is passed in the Authorization header @@ -163,6 +167,8 @@ class UserAuthenticationBackend(AuthenticationBackend): AuthenticatedKhojUser(user, client_application), ) return AuthCredentials(["authenticated"]), AuthenticatedKhojUser(user, client_application) + + # No auth required if server in anonymous mode if state.anonymous_mode: user = await self.khojuser_manager.filter(username="default").prefetch_related("subscription").afirst() if user: @@ -258,28 +264,32 @@ def configure_routes(app): from khoj.routers.api import api from khoj.routers.api_chat import api_chat from khoj.routers.api_config import api_config - from khoj.routers.auth import auth_router from khoj.routers.indexer import indexer from khoj.routers.web_client import web_client app.include_router(api, prefix="/api") + app.include_router(api_chat, prefix="/api/chat") app.include_router(api_config, prefix="/api/config") app.include_router(indexer, prefix="/api/v1/index") app.include_router(web_client) - app.include_router(auth_router, prefix="/auth") - app.include_router(api_chat, prefix="/api/chat") + + if not state.anonymous_mode: + from khoj.routers.auth import auth_router + + app.include_router(auth_router, prefix="/auth") + logger.info("🔑 Enabled Authentication") if state.billing_enabled: from khoj.routers.subscription import subscription_router - logger.info("💳 Enabled Billing") app.include_router(subscription_router, prefix="/api/subscription") + logger.info("💳 Enabled Billing") if is_twilio_enabled(): - logger.info("📞 Enabled Twilio") from khoj.routers.api_phone import api_phone app.include_router(api_phone, prefix="/api/config/phone") + logger.info("📞 Enabled Twilio") def configure_middleware(app): diff --git a/src/khoj/routers/auth.py b/src/khoj/routers/auth.py index 02cd073d..89fef85b 100644 --- a/src/khoj/routers/auth.py +++ b/src/khoj/routers/auth.py @@ -2,10 +2,7 @@ import logging import os from typing import Optional -from authlib.integrations.starlette_client import OAuth, OAuthError from fastapi import APIRouter -from google.auth.transport import requests as google_requests -from google.oauth2 import id_token from starlette.authentication import requires from starlette.config import Config from starlette.requests import Request @@ -17,7 +14,6 @@ from khoj.database.adapters import ( get_khoj_tokens, get_or_create_user, ) -from khoj.database.models import KhojApiUser from khoj.routers.helpers import update_telemetry_state from khoj.utils import state @@ -25,11 +21,23 @@ logger = logging.getLogger(__name__) auth_router = APIRouter() -if not state.anonymous_mode and not (os.environ.get("GOOGLE_CLIENT_ID") and os.environ.get("GOOGLE_CLIENT_SECRET")): - logger.warning( - "🚨 Use --anonymous-mode flag to disable Google OAuth or set GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET environment variables to enable it" - ) -else: + +if not state.anonymous_mode: + missing_requirements = [] + from authlib.integrations.starlette_client import OAuth, OAuthError + + try: + from google.auth.transport import requests as google_requests + from google.oauth2 import id_token + except ImportError: + missing_requirements += ["Install the Khoj production package with `pip install khoj-assistant[prod]`"] + if not os.environ.get("GOOGLE_CLIENT_ID") or not os.environ.get("GOOGLE_CLIENT_SECRET"): + missing_requirements += ["Set your GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET as environment variables"] + if missing_requirements: + requirements_string = "\n - " + "\n - ".join(missing_requirements) + error_msg = f"🚨 Start Khoj with --anonymous-mode flag or to enable authentication:{requirements_string}" + logger.error(error_msg) + config = Config(environ=os.environ) oauth = OAuth(config) diff --git a/src/khoj/routers/subscription.py b/src/khoj/routers/subscription.py index 1ce49e04..2730b775 100644 --- a/src/khoj/routers/subscription.py +++ b/src/khoj/routers/subscription.py @@ -2,16 +2,18 @@ import logging import os from datetime import datetime, timezone -import stripe from asgiref.sync import sync_to_async from fastapi import APIRouter, Request -from fastapi.responses import Response from starlette.authentication import requires from khoj.database import adapters +from khoj.utils import state # Stripe integration for Khoj Cloud Subscription -stripe.api_key = os.getenv("STRIPE_API_KEY") +if state.billing_enabled: + import stripe + + stripe.api_key = os.getenv("STRIPE_API_KEY") endpoint_secret = os.getenv("STRIPE_SIGNING_SECRET") logger = logging.getLogger(__name__) subscription_router = APIRouter() diff --git a/src/khoj/routers/twilio.py b/src/khoj/routers/twilio.py index da0f2c50..758c2722 100644 --- a/src/khoj/routers/twilio.py +++ b/src/khoj/routers/twilio.py @@ -1,8 +1,6 @@ import logging import os -from twilio.rest import Client - from khoj.database.models import KhojUser logger = logging.getLogger(__name__) @@ -13,6 +11,8 @@ verification_service_sid = os.getenv("TWILIO_VERIFICATION_SID") twilio_enabled = account_sid is not None and auth_token is not None and verification_service_sid is not None if twilio_enabled: + from twilio.rest import Client + client = Client(account_sid, auth_token) From 4722da9642dff3b185e4f180e1c891199e06f512 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Wed, 14 Feb 2024 15:51:42 +0530 Subject: [PATCH 3/6] Only enable API token, Whatsapp cards on Web UI when Stripe, Twilio setup --- src/khoj/interface/web/config.html | 6 ++++++ src/khoj/routers/web_client.py | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/khoj/interface/web/config.html b/src/khoj/interface/web/config.html index 54a4de41..385baf06 100644 --- a/src/khoj/interface/web/config.html +++ b/src/khoj/interface/web/config.html @@ -187,8 +187,10 @@ + {% if not anonymous_mode or is_twilio_enabled %}

Clients

+ {% if not anonymous_mode %}
API Key @@ -213,6 +215,8 @@
+ {% endif %} + {% if is_twilio_enabled %}
WhatsApp icon @@ -244,7 +248,9 @@
+ {% endif %}
+ {% endif %} {% if billing_enabled %}

Billing

diff --git a/src/khoj/routers/web_client.py b/src/khoj/routers/web_client.py index 529adbd0..7388084c 100644 --- a/src/khoj/routers/web_client.py +++ b/src/khoj/routers/web_client.py @@ -180,8 +180,8 @@ def config_page(request: Request): "khoj_cloud_subscription_url": os.getenv("KHOJ_CLOUD_SUBSCRIPTION_URL"), "is_active": has_required_scope(request, ["premium"]), "has_documents": has_documents, - "phone_number": user.phone_number, "is_twilio_enabled": is_twilio_enabled(), + "phone_number": user.phone_number, "is_phone_number_verified": user.verified_phone_number, }, ) From e21a8530f3d293b02fb1ae3ec94318e93ca19078 Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Wed, 14 Feb 2024 15:57:32 +0530 Subject: [PATCH 4/6] Move used python packages for test into dev dependency group The test dependency group was being used independently --- pyproject.toml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index aa52e06c..d5ddbd21 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -94,22 +94,18 @@ prod = [ "stripe == 7.3.0", "twilio == 8.11", ] -test = [ +dev = [ + "khoj-assistant[prod]", "pytest >= 7.1.2", + "pytest-xdist[psutil]", + "pytest-django == 4.5.2", + "pytest-asyncio == 0.21.1", "freezegun >= 1.2.0", "factory-boy >= 3.2.1", - "trio >= 0.22.0", - "pytest-xdist", "psutil >= 5.8.0", -] -dev = [ - "khoj-assistant[test]", - "khoj-assistant[prod]", "mypy >= 1.0.1", "black >= 23.1.0", "pre-commit >= 3.0.4", - "pytest-django == 4.5.2", - "pytest-asyncio == 0.21.1", ] [tool.hatch.version] From 4007c871ae7e6459e918cc2b8485bfdf054a498a Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Wed, 14 Feb 2024 17:12:53 +0530 Subject: [PATCH 5/6] Remove unused git dependency from Docker images --- Dockerfile | 2 +- prod.Dockerfile | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 961906b8..c042a6d1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,7 +3,7 @@ FROM ubuntu:jammy LABEL org.opencontainers.image.source https://github.com/khoj-ai/khoj # Install System Dependencies -RUN apt update -y && apt -y install python3-pip git swig +RUN apt update -y && apt -y install python3-pip swig WORKDIR /app diff --git a/prod.Dockerfile b/prod.Dockerfile index 2471d9b8..413835d0 100644 --- a/prod.Dockerfile +++ b/prod.Dockerfile @@ -4,7 +4,9 @@ FROM nvidia/cuda:12.2.0-devel-ubuntu22.04 LABEL org.opencontainers.image.source https://github.com/khoj-ai/khoj # Install System Dependencies -RUN apt update -y && apt -y install python3-pip git libsqlite3-0 ffmpeg libsm6 libxext6 +RUN apt update -y && apt -y install python3-pip libsqlite3-0 ffmpeg libsm6 libxext6 +# Install Optional Dependencies +RUN apt install vim -y WORKDIR /app @@ -18,8 +20,6 @@ RUN sed -i "s/dynamic = \\[\"version\"\\]/version = \"$VERSION\"/" pyproject.tom # Copy Source Code COPY . . -RUN apt install vim -y - # Set the PYTHONPATH environment variable in order for it to find the Django app. ENV PYTHONPATH=/app/src:$PYTHONPATH From 4696577636a212e4ebd8e645a7e210475af1845e Mon Sep 17 00:00:00 2001 From: Debanjum Singh Solanky Date: Wed, 14 Feb 2024 17:32:47 +0530 Subject: [PATCH 6/6] Upgrade python dependencies --- pyproject.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d5ddbd21..c47a5cb2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,7 @@ dependencies = [ "dateparser >= 1.1.1", "defusedxml == 0.7.1", "fastapi >= 0.104.1", - "python-multipart >= 0.0.5", + "python-multipart >= 0.0.7", "jinja2 == 3.1.3", "openai >= 1.0.0", "tiktoken >= 0.3.2", @@ -50,7 +50,7 @@ dependencies = [ "pyyaml == 6.0", "rich >= 13.3.1", "schedule == 1.1.0", - "sentence-transformers == 2.2.2", + "sentence-transformers == 2.3.1", "transformers >= 4.28.0", "torch == 2.0.1", "uvicorn == 0.17.6", @@ -61,7 +61,7 @@ dependencies = [ "bs4 >= 0.0.1", "anyio == 3.7.1", "pymupdf >= 1.23.5", - "django == 4.2.7", + "django == 4.2.10", "authlib == 1.2.1", "gpt4all == 2.1.0; platform_system == 'Linux' and platform_machine == 'x86_64'", "gpt4all == 2.1.0; platform_system == 'Windows' or platform_system == 'Darwin'", @@ -72,7 +72,7 @@ dependencies = [ "gunicorn == 21.2.0", "lxml == 4.9.3", "tzdata == 2023.3", - "rapidocr-onnxruntime == 1.3.8", + "rapidocr-onnxruntime == 1.3.11", "openai-whisper >= 20231117", "django-phonenumber-field == 7.3.0", "phonenumbers == 8.13.27",