khoj 1.30.11.dev64__py3-none-any.whl → 1.32.3.dev34__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 (146) hide show
  1. khoj/configure.py +4 -2
  2. khoj/database/adapters/__init__.py +67 -58
  3. khoj/database/admin.py +9 -9
  4. khoj/database/migrations/0077_chatmodel_alter_agent_chat_model_and_more.py +62 -0
  5. khoj/database/migrations/0078_khojuser_email_verification_code_expiry.py +17 -0
  6. khoj/database/models/__init__.py +9 -8
  7. khoj/interface/compiled/404/index.html +1 -1
  8. khoj/interface/compiled/_next/static/chunks/182-8cd8b17d40e6e989.js +20 -0
  9. khoj/interface/compiled/_next/static/chunks/1915-605f698f2573cfd4.js +1 -0
  10. khoj/interface/compiled/_next/static/chunks/2117-9886e6a0232dc093.js +2 -0
  11. khoj/interface/compiled/_next/static/chunks/2581-455000f8aeb08fc3.js +1 -0
  12. khoj/interface/compiled/_next/static/chunks/3175-b2e522f8ca392f7e.js +3 -0
  13. khoj/interface/compiled/_next/static/chunks/3727.dcea8f2193111552.js +1 -0
  14. khoj/interface/compiled/_next/static/chunks/3789-a09e37a819171a9d.js +1 -0
  15. khoj/interface/compiled/_next/static/chunks/4124-0baa32400521e909.js +1 -0
  16. khoj/interface/compiled/_next/static/chunks/4357-03ea130575287c27.js +1 -0
  17. khoj/interface/compiled/_next/static/chunks/5243-f7f0a2a6e1ac5d28.js +1 -0
  18. khoj/interface/compiled/_next/static/chunks/5427-3e7360c8e6ac9728.js +1 -0
  19. khoj/interface/compiled/_next/static/chunks/{1279-4cb23143aa2c0228.js → 5473-b1cf56dedac6577a.js} +1 -1
  20. khoj/interface/compiled/_next/static/chunks/5477-c5d7eabee28a789a.js +1 -0
  21. khoj/interface/compiled/_next/static/chunks/6065-64db9ad305ba0bcd.js +1 -0
  22. khoj/interface/compiled/_next/static/chunks/8667-d3e5bc726e4ff4e3.js +1 -0
  23. khoj/interface/compiled/_next/static/chunks/9259-27d1ff42af9a43e0.js +1 -0
  24. khoj/interface/compiled/_next/static/chunks/94ca1967.1d9b42d929a1ee8c.js +1 -0
  25. khoj/interface/compiled/_next/static/chunks/{1210.ef7a0f9a7e43da1d.js → 9597.83583248dfbf6e73.js} +1 -1
  26. khoj/interface/compiled/_next/static/chunks/964ecbae.51d6faf8801d15e6.js +1 -0
  27. khoj/interface/compiled/_next/static/chunks/9665-1ab5c8c667b74dca.js +1 -0
  28. khoj/interface/compiled/_next/static/chunks/app/_not-found/{page-cfba071f5a657256.js → page-a834eddae3e235df.js} +1 -1
  29. khoj/interface/compiled/_next/static/chunks/app/agents/layout-e00fb81dca656a10.js +1 -0
  30. khoj/interface/compiled/_next/static/chunks/app/agents/page-ab5ebe4efba9b582.js +1 -0
  31. khoj/interface/compiled/_next/static/chunks/app/automations/{layout-7f1b79a2c67af0b4.js → layout-1fe1537449f43496.js} +1 -1
  32. khoj/interface/compiled/_next/static/chunks/app/automations/page-37d56a7bbfd307df.js +1 -0
  33. khoj/interface/compiled/_next/static/chunks/app/chat/layout-33934fc2d6ae6838.js +1 -0
  34. khoj/interface/compiled/_next/static/chunks/app/chat/page-a0b61f10b0bf6dd5.js +1 -0
  35. khoj/interface/compiled/_next/static/chunks/app/layout-30e7fda7262713ce.js +1 -0
  36. khoj/interface/compiled/_next/static/chunks/app/page-33a3375b1414d1bd.js +1 -0
  37. khoj/interface/compiled/_next/static/chunks/app/search/layout-c02531d586972d7d.js +1 -0
  38. khoj/interface/compiled/_next/static/chunks/app/search/page-bbbfda90fa03c5be.js +1 -0
  39. khoj/interface/compiled/_next/static/chunks/app/settings/layout-d09d6510a45cd4bd.js +1 -0
  40. khoj/interface/compiled/_next/static/chunks/app/settings/page-430db6215e48aea2.js +1 -0
  41. khoj/interface/compiled/_next/static/chunks/app/share/chat/layout-e8e5db7830bf3f47.js +1 -0
  42. khoj/interface/compiled/_next/static/chunks/app/share/chat/page-02dc1f2e2a41e522.js +1 -0
  43. khoj/interface/compiled/_next/static/chunks/d3ac728e-44ebd2a0c99b12a0.js +1 -0
  44. khoj/interface/compiled/_next/static/chunks/{fd9d1056-2e6c8140e79afc3b.js → fd9d1056-4482b99a36fd1673.js} +1 -1
  45. khoj/interface/compiled/_next/static/chunks/main-app-de1f09df97a3cfc7.js +1 -0
  46. khoj/interface/compiled/_next/static/chunks/main-db4bfac6b0a8d00b.js +1 -0
  47. khoj/interface/compiled/_next/static/chunks/pages/{_app-f870474a17b7f2fd.js → _app-3c9ca398d360b709.js} +1 -1
  48. khoj/interface/compiled/_next/static/chunks/pages/{_error-c66a4e8afc46f17b.js → _error-cf5ca766ac8f493f.js} +1 -1
  49. khoj/interface/compiled/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
  50. khoj/interface/compiled/_next/static/chunks/webpack-b0a1b08bb62bdc15.js +1 -0
  51. khoj/interface/compiled/_next/static/css/0f04760e76bba6c1.css +25 -0
  52. khoj/interface/compiled/_next/static/css/37a73b87f02df402.css +1 -0
  53. khoj/interface/compiled/_next/static/css/8e6a3ca11a60b189.css +1 -0
  54. khoj/interface/compiled/_next/static/css/9c164d9727dd8092.css +1 -0
  55. khoj/interface/compiled/_next/static/css/c3acbadc30537d04.css +1 -0
  56. khoj/interface/compiled/_next/static/css/dac88c17aaee5fcf.css +1 -0
  57. khoj/interface/compiled/_next/static/css/df4b47a2d0d85eae.css +1 -0
  58. khoj/interface/compiled/_next/static/css/e546bf5cc4914244.css +1 -0
  59. khoj/interface/compiled/_next/static/mqcIHpVqVWkmBuN0npYHA/_buildManifest.js +1 -0
  60. khoj/interface/compiled/agents/index.html +1 -1
  61. khoj/interface/compiled/agents/index.txt +6 -6
  62. khoj/interface/compiled/automations/index.html +1 -1
  63. khoj/interface/compiled/automations/index.txt +7 -7
  64. khoj/interface/compiled/chat/index.html +1 -1
  65. khoj/interface/compiled/chat/index.txt +6 -6
  66. khoj/interface/compiled/index.html +1 -1
  67. khoj/interface/compiled/index.txt +6 -6
  68. khoj/interface/compiled/search/index.html +1 -1
  69. khoj/interface/compiled/search/index.txt +6 -6
  70. khoj/interface/compiled/settings/index.html +1 -1
  71. khoj/interface/compiled/settings/index.txt +8 -8
  72. khoj/interface/compiled/share/chat/index.html +1 -1
  73. khoj/interface/compiled/share/chat/index.txt +6 -6
  74. khoj/interface/email/magic_link.html +36 -13
  75. khoj/main.py +1 -1
  76. khoj/migrations/migrate_server_pg.py +7 -7
  77. khoj/processor/conversation/anthropic/anthropic_chat.py +5 -7
  78. khoj/processor/conversation/google/gemini_chat.py +5 -7
  79. khoj/processor/conversation/google/utils.py +0 -1
  80. khoj/processor/conversation/offline/chat_model.py +15 -14
  81. khoj/processor/conversation/openai/gpt.py +7 -9
  82. khoj/processor/conversation/openai/utils.py +31 -17
  83. khoj/processor/conversation/prompts.py +65 -49
  84. khoj/processor/conversation/utils.py +46 -44
  85. khoj/processor/tools/online_search.py +49 -2
  86. khoj/routers/api.py +22 -27
  87. khoj/routers/api_agents.py +4 -4
  88. khoj/routers/api_chat.py +33 -13
  89. khoj/routers/api_model.py +4 -4
  90. khoj/routers/auth.py +108 -7
  91. khoj/routers/email.py +10 -14
  92. khoj/routers/helpers.py +187 -143
  93. khoj/routers/web_client.py +1 -1
  94. khoj/utils/constants.py +1 -1
  95. khoj/utils/helpers.py +5 -3
  96. khoj/utils/initialization.py +28 -26
  97. {khoj-1.30.11.dev64.dist-info → khoj-1.32.3.dev34.dist-info}/METADATA +7 -7
  98. {khoj-1.30.11.dev64.dist-info → khoj-1.32.3.dev34.dist-info}/RECORD +102 -99
  99. {khoj-1.30.11.dev64.dist-info → khoj-1.32.3.dev34.dist-info}/WHEEL +1 -1
  100. khoj/interface/compiled/_next/static/67DcUiU9MqkM1fhksWunh/_buildManifest.js +0 -1
  101. khoj/interface/compiled/_next/static/chunks/1459.690bf20e7d7b7090.js +0 -1
  102. khoj/interface/compiled/_next/static/chunks/1603-13cef426e0e650ec.js +0 -1
  103. khoj/interface/compiled/_next/static/chunks/1970-1b63ac1497b03a10.js +0 -1
  104. khoj/interface/compiled/_next/static/chunks/2646-92ba433951d02d52.js +0 -20
  105. khoj/interface/compiled/_next/static/chunks/3072-be830e4f8412b9d2.js +0 -1
  106. khoj/interface/compiled/_next/static/chunks/3463-081c031e873b7966.js +0 -3
  107. khoj/interface/compiled/_next/static/chunks/3690-51312931ba1eae30.js +0 -1
  108. khoj/interface/compiled/_next/static/chunks/3717-b46079dbe9f55694.js +0 -1
  109. khoj/interface/compiled/_next/static/chunks/4504-62ac13e7d94c52f9.js +0 -1
  110. khoj/interface/compiled/_next/static/chunks/4602-460621c3241e0d13.js +0 -1
  111. khoj/interface/compiled/_next/static/chunks/4752-554a3db270186ce3.js +0 -1
  112. khoj/interface/compiled/_next/static/chunks/5512-7cc62049bbe60e11.js +0 -1
  113. khoj/interface/compiled/_next/static/chunks/5538-0ea2d3944ca051e1.js +0 -1
  114. khoj/interface/compiled/_next/static/chunks/7023-e8de2bded4df6539.js +0 -2
  115. khoj/interface/compiled/_next/static/chunks/7592-a09c39a38e60634b.js +0 -1
  116. khoj/interface/compiled/_next/static/chunks/8423-1dda16bc56236523.js +0 -1
  117. khoj/interface/compiled/_next/static/chunks/94ca1967.5584df65931cfe83.js +0 -1
  118. khoj/interface/compiled/_next/static/chunks/964ecbae.ea4eab2a3a835ffe.js +0 -1
  119. khoj/interface/compiled/_next/static/chunks/app/agents/layout-1878cc328ea380bd.js +0 -1
  120. khoj/interface/compiled/_next/static/chunks/app/agents/page-8eead7920b0ff92a.js +0 -1
  121. khoj/interface/compiled/_next/static/chunks/app/automations/page-b5800b5286306140.js +0 -1
  122. khoj/interface/compiled/_next/static/chunks/app/chat/layout-9219a85f3477e722.js +0 -1
  123. khoj/interface/compiled/_next/static/chunks/app/chat/page-d7d2ab93e519f0b2.js +0 -1
  124. khoj/interface/compiled/_next/static/chunks/app/layout-6310c57b674dd6f5.js +0 -1
  125. khoj/interface/compiled/_next/static/chunks/app/page-3c32ad5472f75965.js +0 -1
  126. khoj/interface/compiled/_next/static/chunks/app/search/layout-2ca475462c0b2176.js +0 -1
  127. khoj/interface/compiled/_next/static/chunks/app/search/page-faa998c71eb7ca8e.js +0 -1
  128. khoj/interface/compiled/_next/static/chunks/app/settings/layout-f285795bc3154b8c.js +0 -1
  129. khoj/interface/compiled/_next/static/chunks/app/settings/page-cbe7f56b1f87d77a.js +0 -1
  130. khoj/interface/compiled/_next/static/chunks/app/share/chat/layout-592e8c470f2c2084.js +0 -1
  131. khoj/interface/compiled/_next/static/chunks/app/share/chat/page-cd5757199539bbf2.js +0 -1
  132. khoj/interface/compiled/_next/static/chunks/d3ac728e-a9e3522eef9b6b28.js +0 -1
  133. khoj/interface/compiled/_next/static/chunks/main-1ea5c2e0fdef4626.js +0 -1
  134. khoj/interface/compiled/_next/static/chunks/main-app-6d6ee3495efe03d4.js +0 -1
  135. khoj/interface/compiled/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js +0 -1
  136. khoj/interface/compiled/_next/static/chunks/webpack-616f0694bfe6f6c1.js +0 -1
  137. khoj/interface/compiled/_next/static/css/1f293605f2871853.css +0 -1
  138. khoj/interface/compiled/_next/static/css/3c34171b174cc381.css +0 -25
  139. khoj/interface/compiled/_next/static/css/3cf13271869a4aeb.css +0 -1
  140. khoj/interface/compiled/_next/static/css/592ca99f5122e75a.css +0 -1
  141. khoj/interface/compiled/_next/static/css/5a400c87d295e68a.css +0 -1
  142. khoj/interface/compiled/_next/static/css/80bd6301fc657983.css +0 -1
  143. khoj/interface/compiled/_next/static/css/9c4221ae0779cc04.css +0 -1
  144. /khoj/interface/compiled/_next/static/{67DcUiU9MqkM1fhksWunh → mqcIHpVqVWkmBuN0npYHA}/_ssgManifest.js +0 -0
  145. {khoj-1.30.11.dev64.dist-info → khoj-1.32.3.dev34.dist-info}/entry_points.txt +0 -0
  146. {khoj-1.30.11.dev64.dist-info → khoj-1.32.3.dev34.dist-info}/licenses/LICENSE +0 -0
khoj/configure.py CHANGED
@@ -24,6 +24,7 @@ from starlette.concurrency import run_in_threadpool
24
24
  from starlette.middleware import Middleware
25
25
  from starlette.middleware.authentication import AuthenticationMiddleware
26
26
  from starlette.middleware.base import BaseHTTPMiddleware
27
+ from starlette.middleware.httpsredirect import HTTPSRedirectMiddleware
27
28
  from starlette.middleware.sessions import SessionMiddleware
28
29
  from starlette.requests import HTTPConnection
29
30
  from starlette.types import ASGIApp, Receive, Scope, Send
@@ -43,7 +44,6 @@ from khoj.database.adapters import (
43
44
  from khoj.database.models import ClientApplication, KhojUser, ProcessLock, Subscription
44
45
  from khoj.processor.embeddings import CrossEncoderModel, EmbeddingsModel
45
46
  from khoj.routers.api_content import configure_content, configure_search
46
- from khoj.routers.helpers import update_telemetry_state
47
47
  from khoj.routers.twilio import is_twilio_enabled
48
48
  from khoj.utils import constants, state
49
49
  from khoj.utils.config import SearchType
@@ -343,7 +343,7 @@ def configure_routes(app):
343
343
  logger.info("📞 Enabled Twilio")
344
344
 
345
345
 
346
- def configure_middleware(app):
346
+ def configure_middleware(app, ssl_enabled: bool = False):
347
347
  class NextJsMiddleware(Middleware):
348
348
  async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
349
349
  if scope["type"] == "http" and scope["path"].startswith("/_next"):
@@ -354,6 +354,8 @@ def configure_middleware(app):
354
354
  super().__init__(app)
355
355
  self.app = app
356
356
 
357
+ if ssl_enabled:
358
+ app.add_middleware(HTTPSRedirectMiddleware)
357
359
  app.add_middleware(AsyncCloseConnectionsMiddleware)
358
360
  app.add_middleware(AuthenticationMiddleware, backend=UserAuthenticationBackend())
359
361
  app.add_middleware(NextJsMiddleware)
@@ -36,7 +36,7 @@ from torch import Tensor
36
36
  from khoj.database.models import (
37
37
  Agent,
38
38
  AiModelApi,
39
- ChatModelOptions,
39
+ ChatModel,
40
40
  ClientApplication,
41
41
  Conversation,
42
42
  Entry,
@@ -238,7 +238,9 @@ async def aget_or_create_user_by_email(email: str) -> tuple[KhojUser, bool]:
238
238
  await user.asave()
239
239
 
240
240
  if user:
241
- user.email_verification_code = secrets.token_urlsafe(18)
241
+ # Generate a secure 6-digit numeric code
242
+ user.email_verification_code = f"{secrets.randbelow(1000000):06}"
243
+ user.email_verification_code_expiry = datetime.now(tz=timezone.utc) + timedelta(minutes=5)
242
244
  await user.asave()
243
245
 
244
246
  user_subscription = await Subscription.objects.filter(user=user).afirst()
@@ -267,16 +269,19 @@ async def astart_trial_subscription(user: KhojUser) -> Subscription:
267
269
  return subscription
268
270
 
269
271
 
270
- async def aget_user_validated_by_email_verification_code(code: str) -> KhojUser:
271
- user = await KhojUser.objects.filter(email_verification_code=code).afirst()
272
+ async def aget_user_validated_by_email_verification_code(code: str, email: str) -> tuple[Optional[KhojUser], bool]:
273
+ user = await KhojUser.objects.filter(email_verification_code=code, email=email).afirst()
272
274
  if not user:
273
- return None
275
+ return None, False
276
+
277
+ if user.email_verification_code_expiry < datetime.now(tz=timezone.utc):
278
+ return user, True
274
279
 
275
280
  user.email_verification_code = None
276
281
  user.verified_email = True
277
282
  await user.asave()
278
283
 
279
- return user
284
+ return user, False
280
285
 
281
286
 
282
287
  async def create_user_by_google_token(token: dict) -> KhojUser:
@@ -432,10 +437,14 @@ def is_user_subscribed(user: KhojUser) -> bool:
432
437
  return subscribed
433
438
 
434
439
 
435
- async def get_user_by_email(email: str) -> KhojUser:
440
+ async def aget_user_by_email(email: str) -> KhojUser:
436
441
  return await KhojUser.objects.filter(email=email).afirst()
437
442
 
438
443
 
444
+ def get_user_by_email(email: str) -> KhojUser:
445
+ return KhojUser.objects.filter(email=email).first()
446
+
447
+
439
448
  async def aget_user_by_uuid(uuid: str) -> KhojUser:
440
449
  return await KhojUser.objects.filter(uuid=uuid).afirst()
441
450
 
@@ -736,8 +745,8 @@ class AgentAdapters:
736
745
 
737
746
  @staticmethod
738
747
  def create_default_agent(user: KhojUser):
739
- default_conversation_config = ConversationAdapters.get_default_conversation_config(user)
740
- if default_conversation_config is None:
748
+ default_chat_model = ConversationAdapters.get_default_chat_model(user)
749
+ if default_chat_model is None:
741
750
  logger.info("No default conversation config found, skipping default agent creation")
742
751
  return None
743
752
  default_personality = prompts.personality.format(current_date="placeholder", day_of_week="placeholder")
@@ -746,7 +755,7 @@ class AgentAdapters:
746
755
 
747
756
  if agent:
748
757
  agent.personality = default_personality
749
- agent.chat_model = default_conversation_config
758
+ agent.chat_model = default_chat_model
750
759
  agent.slug = AgentAdapters.DEFAULT_AGENT_SLUG
751
760
  agent.name = AgentAdapters.DEFAULT_AGENT_NAME
752
761
  agent.privacy_level = Agent.PrivacyLevel.PUBLIC
@@ -760,7 +769,7 @@ class AgentAdapters:
760
769
  name=AgentAdapters.DEFAULT_AGENT_NAME,
761
770
  privacy_level=Agent.PrivacyLevel.PUBLIC,
762
771
  managed_by_admin=True,
763
- chat_model=default_conversation_config,
772
+ chat_model=default_chat_model,
764
773
  personality=default_personality,
765
774
  slug=AgentAdapters.DEFAULT_AGENT_SLUG,
766
775
  )
@@ -787,7 +796,7 @@ class AgentAdapters:
787
796
  output_modes: List[str],
788
797
  slug: Optional[str] = None,
789
798
  ):
790
- chat_model_option = await ChatModelOptions.objects.filter(chat_model=chat_model).afirst()
799
+ chat_model_option = await ChatModel.objects.filter(name=chat_model).afirst()
791
800
 
792
801
  # Slug will be None for new agents, which will trigger a new agent creation with a generated, immutable slug
793
802
  agent, created = await Agent.objects.filter(slug=slug, creator=user).aupdate_or_create(
@@ -857,7 +866,7 @@ class ConversationAdapters:
857
866
  agent=conversation.agent,
858
867
  conversation_log=conversation.conversation_log,
859
868
  slug=conversation.slug,
860
- title=conversation.title,
869
+ title=conversation.title if conversation.title else conversation.slug,
861
870
  )
862
871
 
863
872
  @staticmethod
@@ -972,29 +981,29 @@ class ConversationAdapters:
972
981
 
973
982
  @staticmethod
974
983
  @require_valid_user
975
- def has_any_conversation_config(user: KhojUser):
976
- return ChatModelOptions.objects.filter(user=user).exists()
984
+ def has_any_chat_model(user: KhojUser):
985
+ return ChatModel.objects.filter(user=user).exists()
977
986
 
978
987
  @staticmethod
979
- def get_all_conversation_configs():
980
- return ChatModelOptions.objects.all()
988
+ def get_all_chat_models():
989
+ return ChatModel.objects.all()
981
990
 
982
991
  @staticmethod
983
- async def aget_all_conversation_configs():
984
- return await sync_to_async(list)(ChatModelOptions.objects.prefetch_related("ai_model_api").all())
992
+ async def aget_all_chat_models():
993
+ return await sync_to_async(list)(ChatModel.objects.prefetch_related("ai_model_api").all())
985
994
 
986
995
  @staticmethod
987
996
  def get_vision_enabled_config():
988
- conversation_configurations = ConversationAdapters.get_all_conversation_configs()
989
- for config in conversation_configurations:
997
+ chat_models = ConversationAdapters.get_all_chat_models()
998
+ for config in chat_models:
990
999
  if config.vision_enabled:
991
1000
  return config
992
1001
  return None
993
1002
 
994
1003
  @staticmethod
995
1004
  async def aget_vision_enabled_config():
996
- conversation_configurations = await ConversationAdapters.aget_all_conversation_configs()
997
- for config in conversation_configurations:
1005
+ chat_models = await ConversationAdapters.aget_all_chat_models()
1006
+ for config in chat_models:
998
1007
  if config.vision_enabled:
999
1008
  return config
1000
1009
  return None
@@ -1010,7 +1019,7 @@ class ConversationAdapters:
1010
1019
  @staticmethod
1011
1020
  @arequire_valid_user
1012
1021
  async def aset_user_conversation_processor(user: KhojUser, conversation_processor_config_id: int):
1013
- config = await ChatModelOptions.objects.filter(id=conversation_processor_config_id).afirst()
1022
+ config = await ChatModel.objects.filter(id=conversation_processor_config_id).afirst()
1014
1023
  if not config:
1015
1024
  return None
1016
1025
  new_config = await UserConversationConfig.objects.aupdate_or_create(user=user, defaults={"setting": config})
@@ -1026,24 +1035,24 @@ class ConversationAdapters:
1026
1035
  return new_config
1027
1036
 
1028
1037
  @staticmethod
1029
- def get_conversation_config(user: KhojUser):
1038
+ def get_chat_model(user: KhojUser):
1030
1039
  subscribed = is_user_subscribed(user)
1031
1040
  if not subscribed:
1032
- return ConversationAdapters.get_default_conversation_config(user)
1041
+ return ConversationAdapters.get_default_chat_model(user)
1033
1042
  config = UserConversationConfig.objects.filter(user=user).first()
1034
1043
  if config:
1035
1044
  return config.setting
1036
- return ConversationAdapters.get_advanced_conversation_config(user)
1045
+ return ConversationAdapters.get_advanced_chat_model(user)
1037
1046
 
1038
1047
  @staticmethod
1039
- async def aget_conversation_config(user: KhojUser):
1048
+ async def aget_chat_model(user: KhojUser):
1040
1049
  subscribed = await ais_user_subscribed(user)
1041
1050
  if not subscribed:
1042
- return await ConversationAdapters.aget_default_conversation_config(user)
1051
+ return await ConversationAdapters.aget_default_chat_model(user)
1043
1052
  config = await UserConversationConfig.objects.filter(user=user).prefetch_related("setting").afirst()
1044
1053
  if config:
1045
1054
  return config.setting
1046
- return ConversationAdapters.aget_advanced_conversation_config(user)
1055
+ return ConversationAdapters.aget_advanced_chat_model(user)
1047
1056
 
1048
1057
  @staticmethod
1049
1058
  async def aget_voice_model_config(user: KhojUser) -> Optional[VoiceModelOption]:
@@ -1064,7 +1073,7 @@ class ConversationAdapters:
1064
1073
  return VoiceModelOption.objects.first()
1065
1074
 
1066
1075
  @staticmethod
1067
- def get_default_conversation_config(user: KhojUser = None):
1076
+ def get_default_chat_model(user: KhojUser = None):
1068
1077
  """Get default conversation config. Prefer chat model by server admin > user > first created chat model"""
1069
1078
  # Get the server chat settings
1070
1079
  server_chat_settings = ServerChatSettings.objects.first()
@@ -1084,10 +1093,10 @@ class ConversationAdapters:
1084
1093
  return user_chat_settings.setting
1085
1094
 
1086
1095
  # Get the first chat model if even the user chat settings are not set
1087
- return ChatModelOptions.objects.filter().first()
1096
+ return ChatModel.objects.filter().first()
1088
1097
 
1089
1098
  @staticmethod
1090
- async def aget_default_conversation_config(user: KhojUser = None):
1099
+ async def aget_default_chat_model(user: KhojUser = None):
1091
1100
  """Get default conversation config. Prefer chat model by server admin > user > first created chat model"""
1092
1101
  # Get the server chat settings
1093
1102
  server_chat_settings: ServerChatSettings = (
@@ -1117,17 +1126,17 @@ class ConversationAdapters:
1117
1126
  return user_chat_settings.setting
1118
1127
 
1119
1128
  # Get the first chat model if even the user chat settings are not set
1120
- return await ChatModelOptions.objects.filter().prefetch_related("ai_model_api").afirst()
1129
+ return await ChatModel.objects.filter().prefetch_related("ai_model_api").afirst()
1121
1130
 
1122
1131
  @staticmethod
1123
- def get_advanced_conversation_config(user: KhojUser):
1132
+ def get_advanced_chat_model(user: KhojUser):
1124
1133
  server_chat_settings = ServerChatSettings.objects.first()
1125
1134
  if server_chat_settings is not None and server_chat_settings.chat_advanced is not None:
1126
1135
  return server_chat_settings.chat_advanced
1127
- return ConversationAdapters.get_default_conversation_config(user)
1136
+ return ConversationAdapters.get_default_chat_model(user)
1128
1137
 
1129
1138
  @staticmethod
1130
- async def aget_advanced_conversation_config(user: KhojUser = None):
1139
+ async def aget_advanced_chat_model(user: KhojUser = None):
1131
1140
  server_chat_settings: ServerChatSettings = (
1132
1141
  await ServerChatSettings.objects.filter()
1133
1142
  .prefetch_related("chat_advanced", "chat_advanced__ai_model_api")
@@ -1135,7 +1144,7 @@ class ConversationAdapters:
1135
1144
  )
1136
1145
  if server_chat_settings is not None and server_chat_settings.chat_advanced is not None:
1137
1146
  return server_chat_settings.chat_advanced
1138
- return await ConversationAdapters.aget_default_conversation_config(user)
1147
+ return await ConversationAdapters.aget_default_chat_model(user)
1139
1148
 
1140
1149
  @staticmethod
1141
1150
  async def aget_server_webscraper():
@@ -1247,16 +1256,16 @@ class ConversationAdapters:
1247
1256
 
1248
1257
  @staticmethod
1249
1258
  def get_conversation_processor_options():
1250
- return ChatModelOptions.objects.all()
1259
+ return ChatModel.objects.all()
1251
1260
 
1252
1261
  @staticmethod
1253
- def set_conversation_processor_config(user: KhojUser, new_config: ChatModelOptions):
1262
+ def set_user_chat_model(user: KhojUser, chat_model: ChatModel):
1254
1263
  user_conversation_config, _ = UserConversationConfig.objects.get_or_create(user=user)
1255
- user_conversation_config.setting = new_config
1264
+ user_conversation_config.setting = chat_model
1256
1265
  user_conversation_config.save()
1257
1266
 
1258
1267
  @staticmethod
1259
- async def aget_user_conversation_config(user: KhojUser):
1268
+ async def aget_user_chat_model(user: KhojUser):
1260
1269
  config = (
1261
1270
  await UserConversationConfig.objects.filter(user=user).prefetch_related("setting__ai_model_api").afirst()
1262
1271
  )
@@ -1288,33 +1297,33 @@ class ConversationAdapters:
1288
1297
  return random.sample(all_questions, max_results)
1289
1298
 
1290
1299
  @staticmethod
1291
- def get_valid_conversation_config(user: KhojUser, conversation: Conversation):
1300
+ def get_valid_chat_model(user: KhojUser, conversation: Conversation):
1292
1301
  agent: Agent = conversation.agent if AgentAdapters.get_default_agent() != conversation.agent else None
1293
1302
  if agent and agent.chat_model:
1294
- conversation_config = conversation.agent.chat_model
1303
+ chat_model = conversation.agent.chat_model
1295
1304
  else:
1296
- conversation_config = ConversationAdapters.get_conversation_config(user)
1305
+ chat_model = ConversationAdapters.get_chat_model(user)
1297
1306
 
1298
- if conversation_config is None:
1299
- conversation_config = ConversationAdapters.get_default_conversation_config()
1307
+ if chat_model is None:
1308
+ chat_model = ConversationAdapters.get_default_chat_model()
1300
1309
 
1301
- if conversation_config.model_type == ChatModelOptions.ModelType.OFFLINE:
1310
+ if chat_model.model_type == ChatModel.ModelType.OFFLINE:
1302
1311
  if state.offline_chat_processor_config is None or state.offline_chat_processor_config.loaded_model is None:
1303
- chat_model = conversation_config.chat_model
1304
- max_tokens = conversation_config.max_prompt_size
1305
- state.offline_chat_processor_config = OfflineChatProcessorModel(chat_model, max_tokens)
1312
+ chat_model_name = chat_model.name
1313
+ max_tokens = chat_model.max_prompt_size
1314
+ state.offline_chat_processor_config = OfflineChatProcessorModel(chat_model_name, max_tokens)
1306
1315
 
1307
- return conversation_config
1316
+ return chat_model
1308
1317
 
1309
1318
  if (
1310
- conversation_config.model_type
1319
+ chat_model.model_type
1311
1320
  in [
1312
- ChatModelOptions.ModelType.ANTHROPIC,
1313
- ChatModelOptions.ModelType.OPENAI,
1314
- ChatModelOptions.ModelType.GOOGLE,
1321
+ ChatModel.ModelType.ANTHROPIC,
1322
+ ChatModel.ModelType.OPENAI,
1323
+ ChatModel.ModelType.GOOGLE,
1315
1324
  ]
1316
- ) and conversation_config.ai_model_api:
1317
- return conversation_config
1325
+ ) and chat_model.ai_model_api:
1326
+ return chat_model
1318
1327
 
1319
1328
  else:
1320
1329
  raise ValueError("Invalid conversation config - either configure offline chat or openai chat")
khoj/database/admin.py CHANGED
@@ -16,7 +16,7 @@ from unfold import admin as unfold_admin
16
16
  from khoj.database.models import (
17
17
  Agent,
18
18
  AiModelApi,
19
- ChatModelOptions,
19
+ ChatModel,
20
20
  ClientApplication,
21
21
  Conversation,
22
22
  Entry,
@@ -147,7 +147,7 @@ class KhojUserAdmin(UserAdmin, unfold_admin.ModelAdmin):
147
147
  if user.email:
148
148
  host = request.get_host()
149
149
  unique_id = user.email_verification_code
150
- login_url = f"{host}/auth/magic?code={unique_id}"
150
+ login_url = f"{host}/auth/magic?code={unique_id}&email={user.email}"
151
151
  messages.info(request, f"Email login URL for {user.email}: {login_url}")
152
152
 
153
153
  get_email_login_url.short_description = "Get email login URL" # type: ignore
@@ -212,15 +212,15 @@ class KhojUserSubscription(unfold_admin.ModelAdmin):
212
212
  list_filter = ("type",)
213
213
 
214
214
 
215
- @admin.register(ChatModelOptions)
216
- class ChatModelOptionsAdmin(unfold_admin.ModelAdmin):
215
+ @admin.register(ChatModel)
216
+ class ChatModelAdmin(unfold_admin.ModelAdmin):
217
217
  list_display = (
218
218
  "id",
219
- "chat_model",
219
+ "name",
220
220
  "ai_model_api",
221
221
  "max_prompt_size",
222
222
  )
223
- search_fields = ("id", "chat_model", "ai_model_api__name")
223
+ search_fields = ("id", "name", "ai_model_api__name")
224
224
 
225
225
 
226
226
  @admin.register(TextToImageModelConfig)
@@ -385,7 +385,7 @@ class UserConversationConfigAdmin(unfold_admin.ModelAdmin):
385
385
  "get_chat_model",
386
386
  "get_subscription_type",
387
387
  )
388
- search_fields = ("id", "user__email", "setting__chat_model", "user__subscription__type")
388
+ search_fields = ("id", "user__email", "setting__name", "user__subscription__type")
389
389
  ordering = ("-updated_at",)
390
390
 
391
391
  def get_user_email(self, obj):
@@ -395,10 +395,10 @@ class UserConversationConfigAdmin(unfold_admin.ModelAdmin):
395
395
  get_user_email.admin_order_field = "user__email" # type: ignore
396
396
 
397
397
  def get_chat_model(self, obj):
398
- return obj.setting.chat_model if obj.setting else None
398
+ return obj.setting.name if obj.setting else None
399
399
 
400
400
  get_chat_model.short_description = "Chat Model" # type: ignore
401
- get_chat_model.admin_order_field = "setting__chat_model" # type: ignore
401
+ get_chat_model.admin_order_field = "setting__name" # type: ignore
402
402
 
403
403
  def get_subscription_type(self, obj):
404
404
  if hasattr(obj.user, "subscription"):
@@ -0,0 +1,62 @@
1
+ # Generated by Django 5.0.9 on 2024-12-09 04:21
2
+
3
+ import django.db.models.deletion
4
+ from django.db import migrations, models
5
+
6
+
7
+ class Migration(migrations.Migration):
8
+ dependencies = [
9
+ ("database", "0076_rename_openaiprocessorconversationconfig_aimodelapi_and_more"),
10
+ ]
11
+
12
+ operations = [
13
+ migrations.RenameModel(
14
+ old_name="ChatModelOptions",
15
+ new_name="ChatModel",
16
+ ),
17
+ migrations.RenameField(
18
+ model_name="chatmodel",
19
+ old_name="chat_model",
20
+ new_name="name",
21
+ ),
22
+ migrations.AlterField(
23
+ model_name="agent",
24
+ name="chat_model",
25
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="database.chatmodel"),
26
+ ),
27
+ migrations.AlterField(
28
+ model_name="serverchatsettings",
29
+ name="chat_advanced",
30
+ field=models.ForeignKey(
31
+ blank=True,
32
+ default=None,
33
+ null=True,
34
+ on_delete=django.db.models.deletion.CASCADE,
35
+ related_name="chat_advanced",
36
+ to="database.chatmodel",
37
+ ),
38
+ ),
39
+ migrations.AlterField(
40
+ model_name="serverchatsettings",
41
+ name="chat_default",
42
+ field=models.ForeignKey(
43
+ blank=True,
44
+ default=None,
45
+ null=True,
46
+ on_delete=django.db.models.deletion.CASCADE,
47
+ related_name="chat_default",
48
+ to="database.chatmodel",
49
+ ),
50
+ ),
51
+ migrations.AlterField(
52
+ model_name="userconversationconfig",
53
+ name="setting",
54
+ field=models.ForeignKey(
55
+ blank=True,
56
+ default=None,
57
+ null=True,
58
+ on_delete=django.db.models.deletion.CASCADE,
59
+ to="database.chatmodel",
60
+ ),
61
+ ),
62
+ ]
@@ -0,0 +1,17 @@
1
+ # Generated by Django 5.0.9 on 2024-12-14 18:58
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("database", "0077_chatmodel_alter_agent_chat_model_and_more"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.AddField(
13
+ model_name="khojuser",
14
+ name="email_verification_code_expiry",
15
+ field=models.DateTimeField(blank=True, default=None, null=True),
16
+ ),
17
+ ]
@@ -138,6 +138,7 @@ class KhojUser(AbstractUser):
138
138
  verified_phone_number = models.BooleanField(default=False)
139
139
  verified_email = models.BooleanField(default=False)
140
140
  email_verification_code = models.CharField(max_length=200, null=True, default=None, blank=True)
141
+ email_verification_code_expiry = models.DateTimeField(null=True, default=None, blank=True)
141
142
 
142
143
  def save(self, *args, **kwargs):
143
144
  if not self.uuid:
@@ -193,7 +194,7 @@ class AiModelApi(DbBaseModel):
193
194
  return self.name
194
195
 
195
196
 
196
- class ChatModelOptions(DbBaseModel):
197
+ class ChatModel(DbBaseModel):
197
198
  class ModelType(models.TextChoices):
198
199
  OPENAI = "openai"
199
200
  OFFLINE = "offline"
@@ -203,13 +204,13 @@ class ChatModelOptions(DbBaseModel):
203
204
  max_prompt_size = models.IntegerField(default=None, null=True, blank=True)
204
205
  subscribed_max_prompt_size = models.IntegerField(default=None, null=True, blank=True)
205
206
  tokenizer = models.CharField(max_length=200, default=None, null=True, blank=True)
206
- chat_model = models.CharField(max_length=200, default="bartowski/Meta-Llama-3.1-8B-Instruct-GGUF")
207
+ name = models.CharField(max_length=200, default="bartowski/Meta-Llama-3.1-8B-Instruct-GGUF")
207
208
  model_type = models.CharField(max_length=200, choices=ModelType.choices, default=ModelType.OFFLINE)
208
209
  vision_enabled = models.BooleanField(default=False)
209
210
  ai_model_api = models.ForeignKey(AiModelApi, on_delete=models.CASCADE, default=None, null=True, blank=True)
210
211
 
211
212
  def __str__(self):
212
- return self.chat_model
213
+ return self.name
213
214
 
214
215
 
215
216
  class VoiceModelOption(DbBaseModel):
@@ -297,7 +298,7 @@ class Agent(DbBaseModel):
297
298
  models.CharField(max_length=200, choices=OutputModeOptions.choices), default=list, null=True, blank=True
298
299
  )
299
300
  managed_by_admin = models.BooleanField(default=False)
300
- chat_model = models.ForeignKey(ChatModelOptions, on_delete=models.CASCADE)
301
+ chat_model = models.ForeignKey(ChatModel, on_delete=models.CASCADE)
301
302
  slug = models.CharField(max_length=200, unique=True)
302
303
  style_color = models.CharField(max_length=200, choices=StyleColorTypes.choices, default=StyleColorTypes.BLUE)
303
304
  style_icon = models.CharField(max_length=200, choices=StyleIconTypes.choices, default=StyleIconTypes.LIGHTBULB)
@@ -438,10 +439,10 @@ class WebScraper(DbBaseModel):
438
439
 
439
440
  class ServerChatSettings(DbBaseModel):
440
441
  chat_default = models.ForeignKey(
441
- ChatModelOptions, on_delete=models.CASCADE, default=None, null=True, blank=True, related_name="chat_default"
442
+ ChatModel, on_delete=models.CASCADE, default=None, null=True, blank=True, related_name="chat_default"
442
443
  )
443
444
  chat_advanced = models.ForeignKey(
444
- ChatModelOptions, on_delete=models.CASCADE, default=None, null=True, blank=True, related_name="chat_advanced"
445
+ ChatModel, on_delete=models.CASCADE, default=None, null=True, blank=True, related_name="chat_advanced"
445
446
  )
446
447
  web_scraper = models.ForeignKey(
447
448
  WebScraper, on_delete=models.CASCADE, default=None, null=True, blank=True, related_name="web_scraper"
@@ -563,7 +564,7 @@ class SpeechToTextModelOptions(DbBaseModel):
563
564
 
564
565
  class UserConversationConfig(DbBaseModel):
565
566
  user = models.OneToOneField(KhojUser, on_delete=models.CASCADE)
566
- setting = models.ForeignKey(ChatModelOptions, on_delete=models.CASCADE, default=None, null=True, blank=True)
567
+ setting = models.ForeignKey(ChatModel, on_delete=models.CASCADE, default=None, null=True, blank=True)
567
568
 
568
569
 
569
570
  class UserVoiceModelConfig(DbBaseModel):
@@ -638,7 +639,7 @@ def verify_public_conversation(sender, instance, **kwargs):
638
639
 
639
640
  # check if this is a new instance
640
641
  if instance._state.adding:
641
- slug = re.sub(r"\W+", "-", instance.slug.lower())[:50]
642
+ slug = re.sub(r"\W+", "-", instance.slug.lower())[:50] if instance.slug else uuid.uuid4().hex
642
643
  observed_random_id = set()
643
644
  while PublicConversation.objects.filter(slug=slug).exists():
644
645
  try: