diff --git a/docker-compose.yml b/docker-compose.yml index 652cf6a0..960dc1b8 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -22,7 +22,7 @@ services: depends_on: database: condition: service_healthy - # Use the following line to use the latest version of khoj. Otherwise, it will build from source. + # Use the following line to use the latest version of khoj. Otherwise, it will build from source. Set this to ghcr.io/khoj-ai/khoj-cloud if you want to use the prod image. image: ghcr.io/khoj-ai/khoj:latest # Uncomment the following line to build from source. This will take a few minutes. Comment the next two lines out if you want to use the offiicial image. # build: @@ -83,6 +83,7 @@ services: # Telemetry helps us prioritize feature development and understand how people are using Khoj # Read more at https://docs.khoj.dev/miscellaneous/telemetry # - KHOJ_TELEMETRY_DISABLE=True + # Uncomment this line when you're using the official ghcr.io/khoj-ai/khoj-cloud prod image. command: --host="0.0.0.0" --port=42110 -vv --anonymous-mode --non-interactive diff --git a/documentation/docs/clients/emacs.md b/documentation/docs/clients/emacs.md index 73405b69..c047e282 100644 --- a/documentation/docs/clients/emacs.md +++ b/documentation/docs/clients/emacs.md @@ -114,7 +114,7 @@ This feature finds entries similar to the one you are currently on. 2. Hit `C-c s f` (or `M-x khoj RET f`) to find similar entries ### Advanced Usage -- Add [query filters](https://github.com/khoj-ai/khoj/#query-filters) during search to narrow down results further +- Add [query filters](/miscellaneous/advanced#query-filters) during search to narrow down results further e.g. `What is the meaning of life? -"god" +"none" dt>"last week"` - Use `C-c C-o 2` to open the current result at cursor in its source org file diff --git a/pyproject.toml b/pyproject.toml index 59adf952..af3460b5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -64,6 +64,7 @@ dependencies = [ "anyio == 3.7.1", "pymupdf == 1.24.11", "django == 5.0.9", + "django-unfold == 0.42.0", "authlib == 1.2.1", "llama-cpp-python == 0.2.88", "itsdangerous == 2.1.2", diff --git a/src/interface/web/public/assets/icons/khoj_lantern.svg b/src/interface/web/public/assets/icons/khoj_lantern.svg new file mode 100644 index 00000000..d78bddd5 --- /dev/null +++ b/src/interface/web/public/assets/icons/khoj_lantern.svg @@ -0,0 +1,100 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/interface/web/public/assets/icons/khoj_lantern_128x128_dark.png b/src/interface/web/public/assets/icons/khoj_lantern_128x128_dark.png new file mode 100644 index 00000000..8fc673a5 Binary files /dev/null and b/src/interface/web/public/assets/icons/khoj_lantern_128x128_dark.png differ diff --git a/src/khoj/app/settings.py b/src/khoj/app/settings.py index 020b2b2e..708e11d0 100644 --- a/src/khoj/app/settings.py +++ b/src/khoj/app/settings.py @@ -13,6 +13,8 @@ https://docs.djangoproject.com/en/4.2/ref/settings/ import os from pathlib import Path +from django.templatetags.static import static + from khoj.utils.helpers import in_debug_mode, is_env_var_true # Build paths inside the project like this: BASE_DIR / 'subdir'. @@ -72,6 +74,7 @@ INSTALLED_APPS = [ "django.contrib.auth", "django.contrib.contenttypes", "khoj.database.apps.DatabaseConfig", + "unfold", "django.contrib.admin", "django.contrib.sessions", "django.contrib.messages", @@ -195,3 +198,21 @@ APSCHEDULER_DATETIME_FORMAT = "N j, Y, f:s a" # that supports multiple background worker processes instead (e.g. Dramatiq, Celery, Django-RQ, # etc. See: https://djangopackages.org/grids/g/workers-queues-tasks/ for popular options). APSCHEDULER_RUN_NOW_TIMEOUT = 240 # Seconds + +UNFOLD = { + "SITE_TITLE": "Khoj Admin Panel", + "SITE_HEADER": "Khoj Admin Panel", + "SITE_URL": "/", + "SITE_ICON": { + "light": lambda request: static("assets/icons/khoj_lantern_128x128.png"), + "dark": lambda request: static("assets/icons/khoj_lantern_128x128_dark.png"), + }, + "SITE_FAVICONS": [ + { + "rel": "icon", + "sizes": "32x32", + "type": "image/svg+xml", + "href": lambda request: static("assets/icons/khoj_lantern.svg"), + }, + ], +} diff --git a/src/khoj/database/admin.py b/src/khoj/database/admin.py index b71f1f81..73fd5340 100644 --- a/src/khoj/database/admin.py +++ b/src/khoj/database/admin.py @@ -4,11 +4,14 @@ from datetime import date, datetime, timedelta, timezone from apscheduler.job import Job from django.contrib import admin, messages -from django.contrib.auth.admin import UserAdmin +from django.contrib.auth.admin import GroupAdmin as BaseGroupAdmin +from django.contrib.auth.admin import UserAdmin as BaseUserAdmin +from django.contrib.auth.models import Group from django.http import HttpResponse -from django_apscheduler.admin import DjangoJobAdmin +from django_apscheduler.admin import DjangoJobAdmin, DjangoJobExecutionAdmin from django_apscheduler.jobstores import DjangoJobStore -from django_apscheduler.models import DjangoJob +from django_apscheduler.models import DjangoJob, DjangoJobExecution +from unfold import admin as unfold_admin from khoj.database.models import ( Agent, @@ -35,10 +38,8 @@ from khoj.database.models import ( ) from khoj.utils.helpers import ImageIntentType -admin.site.unregister(DjangoJob) - -class KhojDjangoJobAdmin(DjangoJobAdmin): +class KhojDjangoJobAdmin(DjangoJobAdmin, unfold_admin.ModelAdmin): list_display = ( "id", "next_run_time", @@ -62,10 +63,25 @@ class KhojDjangoJobAdmin(DjangoJobAdmin): return queryset, use_distinct +class KhojDjangoJobExecutionAdmin(DjangoJobExecutionAdmin, unfold_admin.ModelAdmin): + pass + + +admin.site.unregister(DjangoJob) admin.site.register(DjangoJob, KhojDjangoJobAdmin) +admin.site.unregister(DjangoJobExecution) +admin.site.register(DjangoJobExecution, KhojDjangoJobExecutionAdmin) -class KhojUserAdmin(UserAdmin): +class GroupAdmin(BaseGroupAdmin, unfold_admin.ModelAdmin): + pass + + +class UserAdmin(BaseUserAdmin, unfold_admin.ModelAdmin): + pass + + +class KhojUserAdmin(UserAdmin, unfold_admin.ModelAdmin): class DateJoinedAfterFilter(admin.SimpleListFilter): title = "Joined after" parameter_name = "joined_after" @@ -137,21 +153,22 @@ class KhojUserAdmin(UserAdmin): get_email_login_url.short_description = "Get email login URL" # type: ignore +admin.site.unregister(Group) admin.site.register(KhojUser, KhojUserAdmin) -admin.site.register(ProcessLock) -admin.site.register(SpeechToTextModelOptions) -admin.site.register(ReflectiveQuestion) -admin.site.register(ClientApplication) -admin.site.register(GithubConfig) -admin.site.register(NotionConfig) -admin.site.register(UserVoiceModelConfig) -admin.site.register(VoiceModelOption) -admin.site.register(UserRequests) +admin.site.register(ProcessLock, unfold_admin.ModelAdmin) +admin.site.register(SpeechToTextModelOptions, unfold_admin.ModelAdmin) +admin.site.register(ReflectiveQuestion, unfold_admin.ModelAdmin) +admin.site.register(ClientApplication, unfold_admin.ModelAdmin) +admin.site.register(GithubConfig, unfold_admin.ModelAdmin) +admin.site.register(NotionConfig, unfold_admin.ModelAdmin) +admin.site.register(UserVoiceModelConfig, unfold_admin.ModelAdmin) +admin.site.register(VoiceModelOption, unfold_admin.ModelAdmin) +admin.site.register(UserRequests, unfold_admin.ModelAdmin) @admin.register(Agent) -class AgentAdmin(admin.ModelAdmin): +class AgentAdmin(unfold_admin.ModelAdmin): list_display = ( "id", "name", @@ -161,7 +178,7 @@ class AgentAdmin(admin.ModelAdmin): @admin.register(Entry) -class EntryAdmin(admin.ModelAdmin): +class EntryAdmin(unfold_admin.ModelAdmin): list_display = ( "id", "created_at", @@ -183,7 +200,7 @@ class EntryAdmin(admin.ModelAdmin): @admin.register(Subscription) -class KhojUserSubscription(admin.ModelAdmin): +class KhojUserSubscription(unfold_admin.ModelAdmin): list_display = ( "id", "user", @@ -195,7 +212,7 @@ class KhojUserSubscription(admin.ModelAdmin): @admin.register(ChatModelOptions) -class ChatModelOptionsAdmin(admin.ModelAdmin): +class ChatModelOptionsAdmin(unfold_admin.ModelAdmin): list_display = ( "id", "chat_model", @@ -206,7 +223,7 @@ class ChatModelOptionsAdmin(admin.ModelAdmin): @admin.register(TextToImageModelConfig) -class TextToImageModelOptionsAdmin(admin.ModelAdmin): +class TextToImageModelOptionsAdmin(unfold_admin.ModelAdmin): list_display = ( "id", "model_name", @@ -216,7 +233,7 @@ class TextToImageModelOptionsAdmin(admin.ModelAdmin): @admin.register(OpenAIProcessorConversationConfig) -class OpenAIProcessorConversationConfigAdmin(admin.ModelAdmin): +class OpenAIProcessorConversationConfigAdmin(unfold_admin.ModelAdmin): list_display = ( "id", "name", @@ -227,7 +244,7 @@ class OpenAIProcessorConversationConfigAdmin(admin.ModelAdmin): @admin.register(SearchModelConfig) -class SearchModelConfigAdmin(admin.ModelAdmin): +class SearchModelConfigAdmin(unfold_admin.ModelAdmin): list_display = ( "id", "name", @@ -238,7 +255,7 @@ class SearchModelConfigAdmin(admin.ModelAdmin): @admin.register(ServerChatSettings) -class ServerChatSettingsAdmin(admin.ModelAdmin): +class ServerChatSettingsAdmin(unfold_admin.ModelAdmin): list_display = ( "chat_default", "chat_advanced", @@ -247,7 +264,7 @@ class ServerChatSettingsAdmin(admin.ModelAdmin): @admin.register(WebScraper) -class WebScraperAdmin(admin.ModelAdmin): +class WebScraperAdmin(unfold_admin.ModelAdmin): list_display = ( "priority", "name", @@ -261,7 +278,7 @@ class WebScraperAdmin(admin.ModelAdmin): @admin.register(Conversation) -class ConversationAdmin(admin.ModelAdmin): +class ConversationAdmin(unfold_admin.ModelAdmin): list_display = ( "id", "user", @@ -360,7 +377,7 @@ class ConversationAdmin(admin.ModelAdmin): @admin.register(UserConversationConfig) -class UserConversationConfigAdmin(admin.ModelAdmin): +class UserConversationConfigAdmin(unfold_admin.ModelAdmin): list_display = ( "id", "get_user_email",