diff --git a/manifest.json b/manifest.json index 4d019834..e0a0a9f5 100644 --- a/manifest.json +++ b/manifest.json @@ -6,5 +6,5 @@ "description": "An AI copilot for your Second Brain", "author": "Khoj Inc.", "authorUrl": "https://github.com/khoj-ai", - "isDesktopOnly": true + "isDesktopOnly": false } diff --git a/prod.Dockerfile b/prod.Dockerfile index a935f3c6..693a3a8b 100644 --- a/prod.Dockerfile +++ b/prod.Dockerfile @@ -20,7 +20,7 @@ COPY . . RUN apt install vim -y # Set the PYTHONPATH environment variable in order for it to find the Django app. -ENV PYTHONPATH=/app/src/khoj:$PYTHONPATH +ENV PYTHONPATH=/app/src:$PYTHONPATH # Run the Application # There are more arguments required for the application to run, diff --git a/src/interface/desktop/chat.html b/src/interface/desktop/chat.html index 21acc4a4..391160fc 100644 --- a/src/interface/desktop/chat.html +++ b/src/interface/desktop/chat.html @@ -120,7 +120,8 @@ // Create a new div for the chat message text and append it to the chat message let chatMessageText = document.createElement('div'); chatMessageText.className = `chat-message-text ${by}`; - chatMessageText.innerHTML = formattedMessage; + let textNode = document.createTextNode(formattedMessage); + chatMessageText.appendChild(textNode); chatMessage.appendChild(chatMessageText); // Append annotations div to the chat message diff --git a/src/interface/desktop/search.html b/src/interface/desktop/search.html index aa8aa662..ce368b9b 100644 --- a/src/interface/desktop/search.html +++ b/src/interface/desktop/search.html @@ -112,14 +112,14 @@ } else if ( item.additional.file.endsWith(".md") || item.additional.file.endsWith(".markdown") || - (item.additional.file.includes("issues") && item.additional.file.includes("github.com")) || - (item.additional.file.includes("commit") && item.additional.file.includes("github.com")) + (item.additional.file.includes("issues") && item.additional.source === "github") || + (item.additional.file.includes("commit") && item.additional.source === "github") ) { html += render_markdown(query, [item]); } else if (item.additional.file.endsWith(".pdf")) { html += render_pdf(query, [item]); - } else if (item.additional.file.includes("notion.so")) { + } else if (item.additional.source == "notion") { html += `
` + `${item.additional.heading}` + `

${item.entry}

` + `
`; } else if (item.additional.file.endsWith(".html")) { html += render_html(query, [item]); diff --git a/src/interface/obsidian/manifest.json b/src/interface/obsidian/manifest.json index 4d019834..e0a0a9f5 100644 --- a/src/interface/obsidian/manifest.json +++ b/src/interface/obsidian/manifest.json @@ -6,5 +6,5 @@ "description": "An AI copilot for your Second Brain", "author": "Khoj Inc.", "authorUrl": "https://github.com/khoj-ai", - "isDesktopOnly": true + "isDesktopOnly": false } diff --git a/src/khoj/app/settings.py b/src/khoj/app/settings.py index 721bcc87..427706e8 100644 --- a/src/khoj/app/settings.py +++ b/src/khoj/app/settings.py @@ -14,7 +14,7 @@ from pathlib import Path import os # Build paths inside the project like this: BASE_DIR / 'subdir'. -BASE_DIR = Path(__file__).resolve().parent.parent.parent.parent +BASE_DIR = Path(__file__).resolve().parent.parent # Quick-start development settings - unsuitable for production @@ -24,15 +24,15 @@ BASE_DIR = Path(__file__).resolve().parent.parent.parent.parent SECRET_KEY = os.getenv("KHOJ_DJANGO_SECRET_KEY") # SECURITY WARNING: don't run with debug turned on in production! -DEBUG = os.getenv("KHOJ_DEBUG", "False") == "True" +DEBUG = os.getenv("KHOJ_DEBUG") == "True" -ALLOWED_HOSTS = [".khoj.dev", "localhost", "127.0.0.1", "[::1]", "beta.khoj.dev"] +# All Subdomains of KHOJ_DOMAIN are trusted +KHOJ_DOMAIN = os.getenv("KHOJ_DOMAIN", "khoj.dev") +ALLOWED_HOSTS = [f".{KHOJ_DOMAIN}", "localhost", "127.0.0.1", "[::1]"] CSRF_TRUSTED_ORIGINS = [ - "https://app.khoj.dev", - "https://beta.khoj.dev", - "https://khoj.dev", - "https://*.khoj.dev", + f"https://*.{KHOJ_DOMAIN}", + f"https://{KHOJ_DOMAIN}", ] COOKIE_SAMESITE = "None" @@ -40,8 +40,8 @@ if DEBUG: SESSION_COOKIE_DOMAIN = "localhost" CSRF_COOKIE_DOMAIN = "localhost" else: - SESSION_COOKIE_DOMAIN = "khoj.dev" - CSRF_COOKIE_DOMAIN = "khoj.dev" + SESSION_COOKIE_DOMAIN = KHOJ_DOMAIN + CSRF_COOKIE_DOMAIN = KHOJ_DOMAIN SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True @@ -143,7 +143,7 @@ USE_TZ = True # https://docs.djangoproject.com/en/4.2/howto/static-files/ STATIC_ROOT = BASE_DIR / "static" -STATICFILES_DIRS = [BASE_DIR / "src/khoj/interface/web"] +STATICFILES_DIRS = [BASE_DIR / "interface/web"] STATIC_URL = "/static/" # Default primary key field type diff --git a/src/khoj/interface/web/search.html b/src/khoj/interface/web/search.html index 5331ea92..d3ddb595 100644 --- a/src/khoj/interface/web/search.html +++ b/src/khoj/interface/web/search.html @@ -112,14 +112,14 @@ } else if ( item.additional.file.endsWith(".md") || item.additional.file.endsWith(".markdown") || - (item.additional.file.includes("issues") && item.additional.file.includes("github.com")) || - (item.additional.file.includes("commit") && item.additional.file.includes("github.com")) + (item.additional.file.includes("issues") && item.additional.source === "github") || + (item.additional.file.includes("commit") && item.additional.source === "github") ) { html += render_markdown(query, [item]); } else if (item.additional.file.endsWith(".pdf")) { html += render_pdf(query, [item]); - } else if (item.additional.file.includes("notion.so")) { + } else if (item.additional.source === "notion") { html += `
` + `${item.additional.heading}` + `

${item.entry}

` + `
`; } else if (item.additional.file.endsWith(".html")) { html += render_html(query, [item]); diff --git a/src/khoj/main.py b/src/khoj/main.py index c5e1f277..c99ef1a1 100644 --- a/src/khoj/main.py +++ b/src/khoj/main.py @@ -3,6 +3,8 @@ """ # Standard Packages +from contextlib import redirect_stdout +import io import os import sys import locale @@ -33,10 +35,14 @@ os.environ.setdefault("DJANGO_SETTINGS_MODULE", "khoj.app.settings") django.setup() # Initialize Django Database -call_command("migrate", "--noinput") +db_migrate_output = io.StringIO() +with redirect_stdout(db_migrate_output): + call_command("migrate", "--noinput") # Initialize Django Static Files -call_command("collectstatic", "--noinput") +collectstatic_output = io.StringIO() +with redirect_stdout(collectstatic_output): + call_command("collectstatic", "--noinput") # Initialize the Application Server app = FastAPI() @@ -45,9 +51,16 @@ app = FastAPI() django_app = get_asgi_application() # Add CORS middleware +KHOJ_DOMAIN = os.getenv("KHOJ_DOMAIN", "app.khoj.dev") app.add_middleware( CORSMiddleware, - allow_origins=["app://obsidian.md", "http://localhost:*", "https://app.khoj.dev/*", "app://khoj.dev"], + allow_origins=[ + "app://obsidian.md", + "http://localhost:*", + "http://127.0.0.1:*", + f"https://{KHOJ_DOMAIN}", + "app://khoj.dev", + ], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], @@ -79,14 +92,16 @@ def run(should_start_server=True): args = cli(state.cli_args) set_state(args) - logger.info(f"🚒 Initializing Khoj v{state.khoj_version}") - # Set Logging Level if args.verbose == 0: logger.setLevel(logging.INFO) elif args.verbose >= 1: logger.setLevel(logging.DEBUG) + logger.info(f"🚒 Initializing Khoj v{state.khoj_version}") + logger.info(f"📦 Initializing DB:\n{db_migrate_output.getvalue().strip()}") + logger.debug(f"🌍 Initializing Web Client:\n{collectstatic_output.getvalue().strip()}") + initialization() # Create app directory, if it doesn't exist @@ -107,10 +122,10 @@ def run(should_start_server=True): # Mount Django and Static Files app.mount("/server", django_app, name="server") - static_dir = "static" + static_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "static") if not os.path.exists(static_dir): os.mkdir(static_dir) - app.mount(f"/{static_dir}", StaticFiles(directory=static_dir), name=static_dir) + app.mount(f"/static", StaticFiles(directory=static_dir), name=static_dir) # Configure Middleware configure_middleware(app) diff --git a/src/khoj/search_type/image_search.py b/src/khoj/search_type/image_search.py index 8c0a3cdb..fd5107ad 100644 --- a/src/khoj/search_type/image_search.py +++ b/src/khoj/search_type/image_search.py @@ -12,7 +12,6 @@ from sentence_transformers import SentenceTransformer, util from PIL import Image from tqdm import trange import torch -from khoj.utils import state # Internal Packages from khoj.utils.helpers import get_absolute_path, get_from_dict, resolve_absolute_path, load_model, timer @@ -26,9 +25,6 @@ logger = logging.getLogger(__name__) def initialize_model(search_config: ImageSearchConfig): - # Initialize Model - torch.set_num_threads(4) - # Convert model directory to absolute path search_config.model_directory = resolve_absolute_path(search_config.model_directory) diff --git a/src/khoj/search_type/text_search.py b/src/khoj/search_type/text_search.py index a78ce522..ca0ac6d3 100644 --- a/src/khoj/search_type/text_search.py +++ b/src/khoj/search_type/text_search.py @@ -147,6 +147,7 @@ def collate_results(hits, dedupe=True): "score": hit.distance, "corpus_id": str(hit.corpus_id), "additional": { + "source": hit.file_source, "file": hit.file_path, "compiled": hit.compiled, "heading": hit.heading, @@ -169,6 +170,7 @@ def deduplicated_search_responses(hits: List[SearchResponse]): "score": hit.score, "corpus_id": hit.corpus_id, "additional": { + "source": hit.additional["source"], "file": hit.additional["file"], "compiled": hit.additional["compiled"], "heading": hit.additional["heading"], diff --git a/src/khoj/utils/rawconfig.py b/src/khoj/utils/rawconfig.py index 4c97aedd..d36a36ff 100644 --- a/src/khoj/utils/rawconfig.py +++ b/src/khoj/utils/rawconfig.py @@ -72,6 +72,9 @@ class ImageSearchConfig(ConfigBase): encoder_type: Optional[str] = None model_directory: Optional[Path] = None + class Config: + protected_namespaces = () + class SearchConfig(ConfigBase): image: Optional[ImageSearchConfig] = None