khoj 1.16.1.dev15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (242) hide show
  1. khoj/__init__.py +0 -0
  2. khoj/app/README.md +94 -0
  3. khoj/app/__init__.py +0 -0
  4. khoj/app/asgi.py +16 -0
  5. khoj/app/settings.py +192 -0
  6. khoj/app/urls.py +25 -0
  7. khoj/configure.py +424 -0
  8. khoj/database/__init__.py +0 -0
  9. khoj/database/adapters/__init__.py +1234 -0
  10. khoj/database/admin.py +290 -0
  11. khoj/database/apps.py +6 -0
  12. khoj/database/management/__init__.py +0 -0
  13. khoj/database/management/commands/__init__.py +0 -0
  14. khoj/database/management/commands/change_generated_images_url.py +61 -0
  15. khoj/database/management/commands/convert_images_png_to_webp.py +99 -0
  16. khoj/database/migrations/0001_khojuser.py +98 -0
  17. khoj/database/migrations/0002_googleuser.py +32 -0
  18. khoj/database/migrations/0003_vector_extension.py +10 -0
  19. khoj/database/migrations/0004_content_types_and_more.py +181 -0
  20. khoj/database/migrations/0005_embeddings_corpus_id.py +19 -0
  21. khoj/database/migrations/0006_embeddingsdates.py +33 -0
  22. khoj/database/migrations/0007_add_conversation.py +27 -0
  23. khoj/database/migrations/0008_alter_conversation_conversation_log.py +17 -0
  24. khoj/database/migrations/0009_khojapiuser.py +24 -0
  25. khoj/database/migrations/0010_chatmodeloptions_and_more.py +83 -0
  26. khoj/database/migrations/0010_rename_embeddings_entry_and_more.py +30 -0
  27. khoj/database/migrations/0011_merge_20231102_0138.py +14 -0
  28. khoj/database/migrations/0012_entry_file_source.py +21 -0
  29. khoj/database/migrations/0013_subscription.py +37 -0
  30. khoj/database/migrations/0014_alter_googleuser_picture.py +17 -0
  31. khoj/database/migrations/0015_alter_subscription_user.py +21 -0
  32. khoj/database/migrations/0016_alter_subscription_renewal_date.py +17 -0
  33. khoj/database/migrations/0017_searchmodel.py +32 -0
  34. khoj/database/migrations/0018_searchmodelconfig_delete_searchmodel.py +30 -0
  35. khoj/database/migrations/0019_alter_googleuser_family_name_and_more.py +27 -0
  36. khoj/database/migrations/0020_reflectivequestion.py +36 -0
  37. khoj/database/migrations/0021_speechtotextmodeloptions_and_more.py +42 -0
  38. khoj/database/migrations/0022_texttoimagemodelconfig.py +25 -0
  39. khoj/database/migrations/0023_usersearchmodelconfig.py +33 -0
  40. khoj/database/migrations/0024_alter_entry_embeddings.py +18 -0
  41. khoj/database/migrations/0025_clientapplication_khojuser_phone_number_and_more.py +46 -0
  42. khoj/database/migrations/0025_searchmodelconfig_embeddings_inference_endpoint_and_more.py +22 -0
  43. khoj/database/migrations/0026_searchmodelconfig_cross_encoder_inference_endpoint_and_more.py +22 -0
  44. khoj/database/migrations/0027_merge_20240118_1324.py +13 -0
  45. khoj/database/migrations/0028_khojuser_verified_phone_number.py +17 -0
  46. khoj/database/migrations/0029_userrequests.py +27 -0
  47. khoj/database/migrations/0030_conversation_slug_and_title.py +38 -0
  48. khoj/database/migrations/0031_agent_conversation_agent.py +53 -0
  49. khoj/database/migrations/0031_alter_googleuser_locale.py +30 -0
  50. khoj/database/migrations/0032_merge_20240322_0427.py +14 -0
  51. khoj/database/migrations/0033_rename_tuning_agent_personality.py +17 -0
  52. khoj/database/migrations/0034_alter_chatmodeloptions_chat_model.py +32 -0
  53. khoj/database/migrations/0035_processlock.py +26 -0
  54. khoj/database/migrations/0036_alter_processlock_name.py +19 -0
  55. khoj/database/migrations/0036_delete_offlinechatprocessorconversationconfig.py +15 -0
  56. khoj/database/migrations/0036_publicconversation.py +42 -0
  57. khoj/database/migrations/0037_chatmodeloptions_openai_config_and_more.py +51 -0
  58. khoj/database/migrations/0037_searchmodelconfig_bi_encoder_docs_encode_config_and_more.py +32 -0
  59. khoj/database/migrations/0038_merge_20240425_0857.py +14 -0
  60. khoj/database/migrations/0038_merge_20240426_1640.py +12 -0
  61. khoj/database/migrations/0039_merge_20240501_0301.py +12 -0
  62. khoj/database/migrations/0040_alter_processlock_name.py +26 -0
  63. khoj/database/migrations/0040_merge_20240504_1010.py +14 -0
  64. khoj/database/migrations/0041_merge_20240505_1234.py +14 -0
  65. khoj/database/migrations/0042_serverchatsettings.py +46 -0
  66. khoj/database/migrations/0043_alter_chatmodeloptions_model_type.py +21 -0
  67. khoj/database/migrations/0044_conversation_file_filters.py +17 -0
  68. khoj/database/migrations/0045_fileobject.py +37 -0
  69. khoj/database/migrations/0046_khojuser_email_verification_code_and_more.py +22 -0
  70. khoj/database/migrations/0047_alter_entry_file_type.py +31 -0
  71. khoj/database/migrations/0048_voicemodeloption_uservoicemodelconfig.py +52 -0
  72. khoj/database/migrations/0049_datastore.py +38 -0
  73. khoj/database/migrations/0049_texttoimagemodelconfig_api_key_and_more.py +58 -0
  74. khoj/database/migrations/0050_alter_processlock_name.py +25 -0
  75. khoj/database/migrations/0051_merge_20240702_1220.py +14 -0
  76. khoj/database/migrations/0052_alter_searchmodelconfig_bi_encoder_docs_encode_config_and_more.py +27 -0
  77. khoj/database/migrations/__init__.py +0 -0
  78. khoj/database/models/__init__.py +402 -0
  79. khoj/database/tests.py +3 -0
  80. khoj/interface/email/feedback.html +34 -0
  81. khoj/interface/email/magic_link.html +17 -0
  82. khoj/interface/email/task.html +40 -0
  83. khoj/interface/email/welcome.html +61 -0
  84. khoj/interface/web/404.html +56 -0
  85. khoj/interface/web/agent.html +312 -0
  86. khoj/interface/web/agents.html +276 -0
  87. khoj/interface/web/assets/icons/agents.svg +6 -0
  88. khoj/interface/web/assets/icons/automation.svg +37 -0
  89. khoj/interface/web/assets/icons/cancel.svg +3 -0
  90. khoj/interface/web/assets/icons/chat.svg +24 -0
  91. khoj/interface/web/assets/icons/collapse.svg +17 -0
  92. khoj/interface/web/assets/icons/computer.png +0 -0
  93. khoj/interface/web/assets/icons/confirm-icon.svg +1 -0
  94. khoj/interface/web/assets/icons/copy-button-success.svg +6 -0
  95. khoj/interface/web/assets/icons/copy-button.svg +5 -0
  96. khoj/interface/web/assets/icons/credit-card.png +0 -0
  97. khoj/interface/web/assets/icons/delete.svg +26 -0
  98. khoj/interface/web/assets/icons/docx.svg +7 -0
  99. khoj/interface/web/assets/icons/edit.svg +4 -0
  100. khoj/interface/web/assets/icons/favicon-128x128.ico +0 -0
  101. khoj/interface/web/assets/icons/favicon-128x128.png +0 -0
  102. khoj/interface/web/assets/icons/favicon-256x256.png +0 -0
  103. khoj/interface/web/assets/icons/favicon.icns +0 -0
  104. khoj/interface/web/assets/icons/github.svg +1 -0
  105. khoj/interface/web/assets/icons/key.svg +4 -0
  106. khoj/interface/web/assets/icons/khoj-logo-sideways-200.png +0 -0
  107. khoj/interface/web/assets/icons/khoj-logo-sideways-500.png +0 -0
  108. khoj/interface/web/assets/icons/khoj-logo-sideways.svg +5385 -0
  109. khoj/interface/web/assets/icons/logotype.svg +1 -0
  110. khoj/interface/web/assets/icons/markdown.svg +1 -0
  111. khoj/interface/web/assets/icons/new.svg +23 -0
  112. khoj/interface/web/assets/icons/notion.svg +4 -0
  113. khoj/interface/web/assets/icons/openai-logomark.svg +1 -0
  114. khoj/interface/web/assets/icons/org.svg +1 -0
  115. khoj/interface/web/assets/icons/pdf.svg +23 -0
  116. khoj/interface/web/assets/icons/pencil-edit.svg +5 -0
  117. khoj/interface/web/assets/icons/plaintext.svg +1 -0
  118. khoj/interface/web/assets/icons/question-mark-icon.svg +1 -0
  119. khoj/interface/web/assets/icons/search.svg +25 -0
  120. khoj/interface/web/assets/icons/send.svg +1 -0
  121. khoj/interface/web/assets/icons/share.svg +8 -0
  122. khoj/interface/web/assets/icons/speaker.svg +4 -0
  123. khoj/interface/web/assets/icons/stop-solid.svg +37 -0
  124. khoj/interface/web/assets/icons/sync.svg +4 -0
  125. khoj/interface/web/assets/icons/thumbs-down-svgrepo-com.svg +6 -0
  126. khoj/interface/web/assets/icons/thumbs-up-svgrepo-com.svg +6 -0
  127. khoj/interface/web/assets/icons/user-silhouette.svg +4 -0
  128. khoj/interface/web/assets/icons/voice.svg +8 -0
  129. khoj/interface/web/assets/icons/web.svg +2 -0
  130. khoj/interface/web/assets/icons/whatsapp.svg +17 -0
  131. khoj/interface/web/assets/khoj.css +237 -0
  132. khoj/interface/web/assets/markdown-it.min.js +8476 -0
  133. khoj/interface/web/assets/natural-cron.min.js +1 -0
  134. khoj/interface/web/assets/org.min.js +1823 -0
  135. khoj/interface/web/assets/pico.min.css +5 -0
  136. khoj/interface/web/assets/purify.min.js +3 -0
  137. khoj/interface/web/assets/samples/desktop-browse-draw-sample.png +0 -0
  138. khoj/interface/web/assets/samples/desktop-plain-chat-sample.png +0 -0
  139. khoj/interface/web/assets/samples/desktop-remember-plan-sample.png +0 -0
  140. khoj/interface/web/assets/samples/phone-browse-draw-sample.png +0 -0
  141. khoj/interface/web/assets/samples/phone-plain-chat-sample.png +0 -0
  142. khoj/interface/web/assets/samples/phone-remember-plan-sample.png +0 -0
  143. khoj/interface/web/assets/utils.js +33 -0
  144. khoj/interface/web/base_config.html +445 -0
  145. khoj/interface/web/chat.html +3546 -0
  146. khoj/interface/web/config.html +1011 -0
  147. khoj/interface/web/config_automation.html +1103 -0
  148. khoj/interface/web/content_source_computer_input.html +139 -0
  149. khoj/interface/web/content_source_github_input.html +216 -0
  150. khoj/interface/web/content_source_notion_input.html +94 -0
  151. khoj/interface/web/khoj.webmanifest +51 -0
  152. khoj/interface/web/login.html +219 -0
  153. khoj/interface/web/public_conversation.html +2006 -0
  154. khoj/interface/web/search.html +470 -0
  155. khoj/interface/web/utils.html +48 -0
  156. khoj/main.py +241 -0
  157. khoj/manage.py +22 -0
  158. khoj/migrations/__init__.py +0 -0
  159. khoj/migrations/migrate_offline_chat_default_model.py +69 -0
  160. khoj/migrations/migrate_offline_chat_default_model_2.py +71 -0
  161. khoj/migrations/migrate_offline_chat_schema.py +83 -0
  162. khoj/migrations/migrate_offline_model.py +29 -0
  163. khoj/migrations/migrate_processor_config_openai.py +67 -0
  164. khoj/migrations/migrate_server_pg.py +138 -0
  165. khoj/migrations/migrate_version.py +17 -0
  166. khoj/processor/__init__.py +0 -0
  167. khoj/processor/content/__init__.py +0 -0
  168. khoj/processor/content/docx/__init__.py +0 -0
  169. khoj/processor/content/docx/docx_to_entries.py +110 -0
  170. khoj/processor/content/github/__init__.py +0 -0
  171. khoj/processor/content/github/github_to_entries.py +224 -0
  172. khoj/processor/content/images/__init__.py +0 -0
  173. khoj/processor/content/images/image_to_entries.py +118 -0
  174. khoj/processor/content/markdown/__init__.py +0 -0
  175. khoj/processor/content/markdown/markdown_to_entries.py +165 -0
  176. khoj/processor/content/notion/notion_to_entries.py +260 -0
  177. khoj/processor/content/org_mode/__init__.py +0 -0
  178. khoj/processor/content/org_mode/org_to_entries.py +231 -0
  179. khoj/processor/content/org_mode/orgnode.py +532 -0
  180. khoj/processor/content/pdf/__init__.py +0 -0
  181. khoj/processor/content/pdf/pdf_to_entries.py +116 -0
  182. khoj/processor/content/plaintext/__init__.py +0 -0
  183. khoj/processor/content/plaintext/plaintext_to_entries.py +122 -0
  184. khoj/processor/content/text_to_entries.py +297 -0
  185. khoj/processor/conversation/__init__.py +0 -0
  186. khoj/processor/conversation/anthropic/__init__.py +0 -0
  187. khoj/processor/conversation/anthropic/anthropic_chat.py +206 -0
  188. khoj/processor/conversation/anthropic/utils.py +114 -0
  189. khoj/processor/conversation/offline/__init__.py +0 -0
  190. khoj/processor/conversation/offline/chat_model.py +231 -0
  191. khoj/processor/conversation/offline/utils.py +78 -0
  192. khoj/processor/conversation/offline/whisper.py +15 -0
  193. khoj/processor/conversation/openai/__init__.py +0 -0
  194. khoj/processor/conversation/openai/gpt.py +187 -0
  195. khoj/processor/conversation/openai/utils.py +129 -0
  196. khoj/processor/conversation/openai/whisper.py +13 -0
  197. khoj/processor/conversation/prompts.py +758 -0
  198. khoj/processor/conversation/utils.py +262 -0
  199. khoj/processor/embeddings.py +117 -0
  200. khoj/processor/speech/__init__.py +0 -0
  201. khoj/processor/speech/text_to_speech.py +51 -0
  202. khoj/processor/tools/__init__.py +0 -0
  203. khoj/processor/tools/online_search.py +225 -0
  204. khoj/routers/__init__.py +0 -0
  205. khoj/routers/api.py +626 -0
  206. khoj/routers/api_agents.py +43 -0
  207. khoj/routers/api_chat.py +1180 -0
  208. khoj/routers/api_config.py +434 -0
  209. khoj/routers/api_phone.py +86 -0
  210. khoj/routers/auth.py +181 -0
  211. khoj/routers/email.py +133 -0
  212. khoj/routers/helpers.py +1188 -0
  213. khoj/routers/indexer.py +349 -0
  214. khoj/routers/notion.py +91 -0
  215. khoj/routers/storage.py +35 -0
  216. khoj/routers/subscription.py +104 -0
  217. khoj/routers/twilio.py +36 -0
  218. khoj/routers/web_client.py +471 -0
  219. khoj/search_filter/__init__.py +0 -0
  220. khoj/search_filter/base_filter.py +15 -0
  221. khoj/search_filter/date_filter.py +217 -0
  222. khoj/search_filter/file_filter.py +30 -0
  223. khoj/search_filter/word_filter.py +29 -0
  224. khoj/search_type/__init__.py +0 -0
  225. khoj/search_type/text_search.py +241 -0
  226. khoj/utils/__init__.py +0 -0
  227. khoj/utils/cli.py +93 -0
  228. khoj/utils/config.py +81 -0
  229. khoj/utils/constants.py +24 -0
  230. khoj/utils/fs_syncer.py +249 -0
  231. khoj/utils/helpers.py +418 -0
  232. khoj/utils/initialization.py +146 -0
  233. khoj/utils/jsonl.py +43 -0
  234. khoj/utils/models.py +47 -0
  235. khoj/utils/rawconfig.py +160 -0
  236. khoj/utils/state.py +46 -0
  237. khoj/utils/yaml.py +43 -0
  238. khoj-1.16.1.dev15.dist-info/METADATA +178 -0
  239. khoj-1.16.1.dev15.dist-info/RECORD +242 -0
  240. khoj-1.16.1.dev15.dist-info/WHEEL +4 -0
  241. khoj-1.16.1.dev15.dist-info/entry_points.txt +2 -0
  242. khoj-1.16.1.dev15.dist-info/licenses/LICENSE +661 -0
khoj/configure.py ADDED
@@ -0,0 +1,424 @@
1
+ import json
2
+ import logging
3
+ import os
4
+ from datetime import datetime
5
+ from enum import Enum
6
+ from typing import Optional
7
+
8
+ import openai
9
+ import requests
10
+ import schedule
11
+ from asgiref.sync import sync_to_async
12
+ from django.conf import settings
13
+ from django.db import close_old_connections, connections
14
+ from django.utils.timezone import make_aware
15
+ from fastapi import Response
16
+ from starlette.authentication import (
17
+ AuthCredentials,
18
+ AuthenticationBackend,
19
+ SimpleUser,
20
+ UnauthenticatedUser,
21
+ )
22
+ from starlette.concurrency import run_in_threadpool
23
+ from starlette.middleware import Middleware
24
+ from starlette.middleware.authentication import AuthenticationMiddleware
25
+ from starlette.middleware.base import BaseHTTPMiddleware
26
+ from starlette.middleware.sessions import SessionMiddleware
27
+ from starlette.requests import HTTPConnection
28
+ from starlette.types import ASGIApp, Receive, Scope, Send
29
+
30
+ from khoj.database.adapters import (
31
+ AgentAdapters,
32
+ ClientApplicationAdapters,
33
+ ConversationAdapters,
34
+ ProcessLockAdapters,
35
+ SubscriptionState,
36
+ aget_or_create_user_by_phone_number,
37
+ aget_user_by_phone_number,
38
+ aget_user_subscription_state,
39
+ delete_user_requests,
40
+ get_all_users,
41
+ get_or_create_search_models,
42
+ )
43
+ from khoj.database.models import ClientApplication, KhojUser, ProcessLock, Subscription
44
+ from khoj.processor.embeddings import CrossEncoderModel, EmbeddingsModel
45
+ from khoj.routers.indexer import configure_content, configure_search
46
+ from khoj.routers.twilio import is_twilio_enabled
47
+ from khoj.utils import constants, state
48
+ from khoj.utils.config import SearchType
49
+ from khoj.utils.fs_syncer import collect_files
50
+ from khoj.utils.helpers import is_none_or_empty, telemetry_disabled
51
+ from khoj.utils.rawconfig import FullConfig
52
+
53
+ logger = logging.getLogger(__name__)
54
+
55
+
56
+ class AuthenticatedKhojUser(SimpleUser):
57
+ def __init__(self, user, client_app: Optional[ClientApplication] = None):
58
+ self.object = user
59
+ self.client_app = client_app
60
+ super().__init__(user.username)
61
+
62
+
63
+ class AsyncCloseConnectionsMiddleware(BaseHTTPMiddleware):
64
+ """
65
+ Using this middleware to call close_old_connections() twice is a pretty yucky hack,
66
+ as it appears that run_in_threadpool (used by Starlette/FastAPI) and sync_to_async
67
+ (used by Django) have divergent behavior, ultimately acquiring the incorrect thread
68
+ in mixed sync/async which has the effect of duplicating connections.
69
+ We could fix the duplicate connections too if we normalized the thread behavior,
70
+ but at minimum we need to clean up connections in each case to prevent persistent
71
+ "InterfaceError: connection already closed" errors when the database connection is
72
+ reset via a database restart or something -- so here we are!
73
+ If we always use smart_sync_to_async(), this double calling isn't necessary, but
74
+ depending on what levels of abstraction we introduce, we might silently break the
75
+ assumptions. Better to be safe than sorry!
76
+ Attribution: https://gist.github.com/bryanhelmig/6fb091f23c1a4b7462dddce51cfaa1ca
77
+ """
78
+
79
+ async def dispatch(self, request, call_next):
80
+ await run_in_threadpool(close_old_connections)
81
+ await sync_to_async(close_old_connections)()
82
+ try:
83
+ response = await call_next(request)
84
+ finally:
85
+ # in tests, use @override_settings(CLOSE_CONNECTIONS_AFTER_REQUEST=True)
86
+ if getattr(settings, "CLOSE_CONNECTIONS_AFTER_REQUEST", False):
87
+ await run_in_threadpool(connections.close_all)
88
+ await sync_to_async(connections.close_all)()
89
+ return response
90
+
91
+
92
+ class UserAuthenticationBackend(AuthenticationBackend):
93
+ def __init__(
94
+ self,
95
+ ):
96
+ from khoj.database.models import KhojApiUser, KhojUser
97
+
98
+ self.khojuser_manager = KhojUser.objects
99
+ self.khojapiuser_manager = KhojApiUser.objects
100
+ self._initialize_default_user()
101
+ super().__init__()
102
+
103
+ def _initialize_default_user(self):
104
+ if not self.khojuser_manager.filter(username="default").exists():
105
+ default_user = self.khojuser_manager.create_user(
106
+ username="default",
107
+ email="default@example.com",
108
+ password="default",
109
+ )
110
+ renewal_date = make_aware(datetime.strptime("2100-04-01", "%Y-%m-%d"))
111
+ Subscription.objects.create(user=default_user, type="standard", renewal_date=renewal_date)
112
+
113
+ async def authenticate(self, request: HTTPConnection):
114
+ current_user = request.session.get("user")
115
+ if current_user and current_user.get("email"):
116
+ user = (
117
+ await self.khojuser_manager.filter(email=current_user.get("email"))
118
+ .prefetch_related("subscription")
119
+ .afirst()
120
+ )
121
+ if user:
122
+ if not state.billing_enabled:
123
+ return AuthCredentials(["authenticated", "premium"]), AuthenticatedKhojUser(user)
124
+
125
+ subscription_state = await aget_user_subscription_state(user)
126
+ subscribed = (
127
+ subscription_state == SubscriptionState.SUBSCRIBED.value
128
+ or subscription_state == SubscriptionState.TRIAL.value
129
+ or subscription_state == SubscriptionState.UNSUBSCRIBED.value
130
+ )
131
+ if subscribed:
132
+ return AuthCredentials(["authenticated", "premium"]), AuthenticatedKhojUser(user)
133
+ return AuthCredentials(["authenticated"]), AuthenticatedKhojUser(user)
134
+
135
+ # Request from Desktop, Emacs, Obsidian clients
136
+ if len(request.headers.get("Authorization", "").split("Bearer ")) == 2:
137
+ # Get bearer token from header
138
+ bearer_token = request.headers["Authorization"].split("Bearer ")[1]
139
+ # Get user owning token
140
+ user_with_token = (
141
+ await self.khojapiuser_manager.filter(token=bearer_token)
142
+ .select_related("user")
143
+ .prefetch_related("user__subscription")
144
+ .afirst()
145
+ )
146
+ if user_with_token:
147
+ if not state.billing_enabled:
148
+ return AuthCredentials(["authenticated", "premium"]), AuthenticatedKhojUser(user_with_token.user)
149
+
150
+ subscription_state = await aget_user_subscription_state(user_with_token.user)
151
+ subscribed = (
152
+ subscription_state == SubscriptionState.SUBSCRIBED.value
153
+ or subscription_state == SubscriptionState.TRIAL.value
154
+ or subscription_state == SubscriptionState.UNSUBSCRIBED.value
155
+ )
156
+ if subscribed:
157
+ return AuthCredentials(["authenticated", "premium"]), AuthenticatedKhojUser(user_with_token.user)
158
+ return AuthCredentials(["authenticated"]), AuthenticatedKhojUser(user_with_token.user)
159
+
160
+ # Request from Whatsapp client
161
+ client_id = request.query_params.get("client_id")
162
+ if client_id:
163
+ # Get the client secret, which is passed in the Authorization header
164
+ client_secret = request.headers["Authorization"].split("Bearer ")[1]
165
+ if not client_secret:
166
+ return Response(
167
+ status_code=401,
168
+ content="Please provide a client secret in the Authorization header with a client_id query param.",
169
+ )
170
+
171
+ # Get the client application
172
+ client_application = await ClientApplicationAdapters.aget_client_application_by_id(client_id, client_secret)
173
+ if client_application is None:
174
+ return AuthCredentials(), UnauthenticatedUser()
175
+ # Get the identifier used for the user
176
+ phone_number = request.query_params.get("phone_number")
177
+ if is_none_or_empty(phone_number):
178
+ return AuthCredentials(), UnauthenticatedUser()
179
+
180
+ if not phone_number.startswith("+"):
181
+ phone_number = f"+{phone_number}"
182
+
183
+ create_if_not_exists = request.query_params.get("create_if_not_exists")
184
+ if create_if_not_exists:
185
+ user = await aget_or_create_user_by_phone_number(phone_number)
186
+ else:
187
+ user = await aget_user_by_phone_number(phone_number)
188
+
189
+ if user is None:
190
+ return AuthCredentials(), UnauthenticatedUser()
191
+
192
+ if not state.billing_enabled:
193
+ return AuthCredentials(["authenticated", "premium"]), AuthenticatedKhojUser(user, client_application)
194
+
195
+ subscription_state = await aget_user_subscription_state(user)
196
+ subscribed = (
197
+ subscription_state == SubscriptionState.SUBSCRIBED.value
198
+ or subscription_state == SubscriptionState.TRIAL.value
199
+ or subscription_state == SubscriptionState.UNSUBSCRIBED.value
200
+ )
201
+ if subscribed:
202
+ return (
203
+ AuthCredentials(["authenticated", "premium"]),
204
+ AuthenticatedKhojUser(user, client_application),
205
+ )
206
+ return AuthCredentials(["authenticated"]), AuthenticatedKhojUser(user, client_application)
207
+
208
+ # No auth required if server in anonymous mode
209
+ if state.anonymous_mode:
210
+ user = await self.khojuser_manager.filter(username="default").prefetch_related("subscription").afirst()
211
+ if user:
212
+ return AuthCredentials(["authenticated", "premium"]), AuthenticatedKhojUser(user)
213
+
214
+ return AuthCredentials(), UnauthenticatedUser()
215
+
216
+
217
+ def initialize_server(config: Optional[FullConfig]):
218
+ try:
219
+ configure_server(config, init=True)
220
+ except Exception as e:
221
+ logger.error(f"🚨 Failed to configure server on app load: {e}", exc_info=True)
222
+
223
+
224
+ def configure_server(
225
+ config: FullConfig,
226
+ regenerate: bool = False,
227
+ search_type: Optional[SearchType] = None,
228
+ init=False,
229
+ user: KhojUser = None,
230
+ ):
231
+ # Update Config
232
+ if config == None:
233
+ logger.info(f"🚨 Khoj is not configured.\nInitializing it with a default config.")
234
+ config = FullConfig()
235
+ state.config = config
236
+
237
+ if ConversationAdapters.has_valid_openai_conversation_config():
238
+ openai_config = ConversationAdapters.get_openai_conversation_config()
239
+ state.openai_client = openai.OpenAI(api_key=openai_config.api_key)
240
+
241
+ # Initialize Search Models from Config and initialize content
242
+ try:
243
+ search_models = get_or_create_search_models()
244
+ state.embeddings_model = dict()
245
+ state.cross_encoder_model = dict()
246
+
247
+ for model in search_models:
248
+ state.embeddings_model.update(
249
+ {
250
+ model.name: EmbeddingsModel(
251
+ model.bi_encoder,
252
+ model.embeddings_inference_endpoint,
253
+ model.embeddings_inference_endpoint_api_key,
254
+ query_encode_kwargs=model.bi_encoder_query_encode_config,
255
+ docs_encode_kwargs=model.bi_encoder_docs_encode_config,
256
+ model_kwargs=model.bi_encoder_model_config,
257
+ )
258
+ }
259
+ )
260
+ state.cross_encoder_model.update(
261
+ {
262
+ model.name: CrossEncoderModel(
263
+ model.cross_encoder,
264
+ model.cross_encoder_inference_endpoint,
265
+ model.cross_encoder_inference_endpoint_api_key,
266
+ )
267
+ }
268
+ )
269
+
270
+ state.SearchType = configure_search_types()
271
+ state.search_models = configure_search(state.search_models, state.config.search_type)
272
+ setup_default_agent()
273
+
274
+ message = "📡 Telemetry disabled" if telemetry_disabled(state.config.app) else "📡 Telemetry enabled"
275
+ logger.info(message)
276
+
277
+ if not init:
278
+ initialize_content(regenerate, search_type, user)
279
+
280
+ except Exception as e:
281
+ raise e
282
+
283
+
284
+ def setup_default_agent():
285
+ AgentAdapters.create_default_agent()
286
+
287
+
288
+ def initialize_content(regenerate: bool, search_type: Optional[SearchType] = None, user: KhojUser = None):
289
+ # Initialize Content from Config
290
+ if state.search_models:
291
+ try:
292
+ logger.info("📬 Updating content index...")
293
+ all_files = collect_files(user=user)
294
+ status = configure_content(
295
+ all_files,
296
+ regenerate,
297
+ search_type,
298
+ user=user,
299
+ )
300
+ if not status:
301
+ raise RuntimeError("Failed to update content index")
302
+ except Exception as e:
303
+ raise e
304
+
305
+
306
+ def configure_routes(app):
307
+ # Import APIs here to setup search types before while configuring server
308
+ from khoj.routers.api import api
309
+ from khoj.routers.api_agents import api_agents
310
+ from khoj.routers.api_chat import api_chat
311
+ from khoj.routers.api_config import api_config
312
+ from khoj.routers.indexer import indexer
313
+ from khoj.routers.notion import notion_router
314
+ from khoj.routers.web_client import web_client
315
+
316
+ app.include_router(api, prefix="/api")
317
+ app.include_router(api_chat, prefix="/api/chat")
318
+ app.include_router(api_agents, prefix="/api/agents")
319
+ app.include_router(api_config, prefix="/api/config")
320
+ app.include_router(indexer, prefix="/api/v1/index")
321
+ app.include_router(notion_router, prefix="/api/notion")
322
+ app.include_router(web_client)
323
+
324
+ if not state.anonymous_mode:
325
+ from khoj.routers.auth import auth_router
326
+
327
+ app.include_router(auth_router, prefix="/auth")
328
+ logger.info("🔑 Enabled Authentication")
329
+
330
+ if state.billing_enabled:
331
+ from khoj.routers.subscription import subscription_router
332
+
333
+ app.include_router(subscription_router, prefix="/api/subscription")
334
+ logger.info("💳 Enabled Billing")
335
+
336
+ if is_twilio_enabled():
337
+ from khoj.routers.api_phone import api_phone
338
+
339
+ app.include_router(api_phone, prefix="/api/config/phone")
340
+ logger.info("📞 Enabled Twilio")
341
+
342
+
343
+ def configure_middleware(app):
344
+ class NextJsMiddleware(Middleware):
345
+ async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
346
+ if scope["type"] == "http" and scope["path"].startswith("/_next"):
347
+ scope["path"] = "/static" + scope["path"]
348
+ await self.app(scope, receive, send)
349
+
350
+ def __init__(self, app: ASGIApp) -> None:
351
+ super().__init__(app)
352
+ self.app = app
353
+
354
+ app.add_middleware(AsyncCloseConnectionsMiddleware)
355
+ app.add_middleware(AuthenticationMiddleware, backend=UserAuthenticationBackend())
356
+ app.add_middleware(NextJsMiddleware)
357
+ app.add_middleware(SessionMiddleware, secret_key=os.environ.get("KHOJ_DJANGO_SECRET_KEY", "!secret"))
358
+
359
+
360
+ def update_content_index():
361
+ for user in get_all_users():
362
+ all_files = collect_files(user=user)
363
+ success = configure_content(all_files, user=user)
364
+ all_files = collect_files(user=None)
365
+ success = configure_content(all_files, user=None)
366
+ if not success:
367
+ raise RuntimeError("Failed to update content index")
368
+ logger.info("📪 Content index updated via Scheduler")
369
+
370
+
371
+ @schedule.repeat(schedule.every(22).to(25).hours)
372
+ def update_content_index_regularly():
373
+ ProcessLockAdapters.run_with_lock(
374
+ update_content_index, ProcessLock.Operation.INDEX_CONTENT, max_duration_in_seconds=60 * 60 * 2
375
+ )
376
+
377
+
378
+ def configure_search_types():
379
+ # Extract core search types
380
+ core_search_types = {e.name: e.value for e in SearchType}
381
+
382
+ # Dynamically generate search type enum by merging core search types with configured plugin search types
383
+ return Enum("SearchType", core_search_types)
384
+
385
+
386
+ @schedule.repeat(schedule.every(2).minutes)
387
+ def upload_telemetry():
388
+ if telemetry_disabled(state.config.app) or not state.telemetry:
389
+ return
390
+
391
+ try:
392
+ logger.info(f"📡 Uploading telemetry to {constants.telemetry_server}...")
393
+ logger.debug(f"Telemetry state:\n{state.telemetry}")
394
+ for log in state.telemetry:
395
+ for field in log:
396
+ # Check if the value for the field is JSON serializable
397
+ if log[field] is None:
398
+ log[field] = ""
399
+ try:
400
+ json.dumps(log[field])
401
+ except TypeError:
402
+ log[field] = str(log[field])
403
+ response = requests.post(constants.telemetry_server, json=state.telemetry)
404
+ response.raise_for_status()
405
+ except Exception as e:
406
+ logger.error(f"📡 Error uploading telemetry: {e}", exc_info=True)
407
+ else:
408
+ state.telemetry = []
409
+
410
+
411
+ @schedule.repeat(schedule.every(31).minutes)
412
+ def delete_old_user_requests():
413
+ num_deleted = delete_user_requests()
414
+ logger.debug(f"🗑️ Deleted {num_deleted[0]} day-old user requests")
415
+
416
+
417
+ @schedule.repeat(schedule.every(17).minutes)
418
+ def wakeup_scheduler():
419
+ # Wake up the scheduler to ensure it runs the scheduled tasks. This is because the elected leader may not always be aware of tasks scheduled on other workers.
420
+ if state.schedule_leader_process_lock:
421
+ state.scheduler.wakeup()
422
+ else:
423
+ # Make sure the other workers don't run the scheduled tasks
424
+ state.scheduler.pause()
File without changes