khoj 1.16.1.dev15__py3-none-any.whl → 1.17.1.dev220__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 (53) hide show
  1. khoj/configure.py +6 -6
  2. khoj/database/adapters/__init__.py +56 -12
  3. khoj/database/migrations/0053_agent_style_color_agent_style_icon.py +61 -0
  4. khoj/database/migrations/0054_alter_agent_style_color.py +38 -0
  5. khoj/database/models/__init__.py +35 -0
  6. khoj/interface/web/assets/icons/favicon-128x128.png +0 -0
  7. khoj/interface/web/assets/icons/favicon-256x256.png +0 -0
  8. khoj/interface/web/assets/icons/khoj-logo-sideways-200.png +0 -0
  9. khoj/interface/web/assets/icons/khoj-logo-sideways-500.png +0 -0
  10. khoj/interface/web/assets/icons/khoj-logo-sideways.svg +31 -5384
  11. khoj/interface/web/assets/icons/khoj.svg +26 -0
  12. khoj/interface/web/chat.html +191 -301
  13. khoj/interface/web/content_source_computer_input.html +3 -3
  14. khoj/interface/web/content_source_github_input.html +1 -1
  15. khoj/interface/web/content_source_notion_input.html +1 -1
  16. khoj/interface/web/public_conversation.html +1 -1
  17. khoj/interface/web/search.html +2 -2
  18. khoj/interface/web/{config.html → settings.html} +30 -30
  19. khoj/interface/web/utils.html +1 -1
  20. khoj/processor/content/docx/docx_to_entries.py +4 -9
  21. khoj/processor/content/github/github_to_entries.py +1 -3
  22. khoj/processor/content/images/image_to_entries.py +4 -9
  23. khoj/processor/content/markdown/markdown_to_entries.py +4 -9
  24. khoj/processor/content/notion/notion_to_entries.py +1 -3
  25. khoj/processor/content/org_mode/org_to_entries.py +4 -9
  26. khoj/processor/content/pdf/pdf_to_entries.py +4 -9
  27. khoj/processor/content/plaintext/plaintext_to_entries.py +4 -9
  28. khoj/processor/content/text_to_entries.py +1 -3
  29. khoj/processor/conversation/anthropic/anthropic_chat.py +10 -4
  30. khoj/processor/conversation/offline/chat_model.py +19 -7
  31. khoj/processor/conversation/offline/utils.py +2 -0
  32. khoj/processor/conversation/openai/gpt.py +9 -3
  33. khoj/processor/conversation/prompts.py +56 -25
  34. khoj/processor/conversation/utils.py +5 -6
  35. khoj/processor/tools/online_search.py +13 -7
  36. khoj/routers/api.py +60 -10
  37. khoj/routers/api_agents.py +3 -1
  38. khoj/routers/api_chat.py +335 -562
  39. khoj/routers/api_content.py +538 -0
  40. khoj/routers/api_model.py +156 -0
  41. khoj/routers/helpers.py +339 -26
  42. khoj/routers/notion.py +2 -8
  43. khoj/routers/web_client.py +43 -256
  44. khoj/search_type/text_search.py +5 -4
  45. khoj/utils/fs_syncer.py +4 -2
  46. khoj/utils/rawconfig.py +6 -1
  47. {khoj-1.16.1.dev15.dist-info → khoj-1.17.1.dev220.dist-info}/METADATA +3 -3
  48. {khoj-1.16.1.dev15.dist-info → khoj-1.17.1.dev220.dist-info}/RECORD +51 -48
  49. khoj/routers/api_config.py +0 -434
  50. khoj/routers/indexer.py +0 -349
  51. {khoj-1.16.1.dev15.dist-info → khoj-1.17.1.dev220.dist-info}/WHEEL +0 -0
  52. {khoj-1.16.1.dev15.dist-info → khoj-1.17.1.dev220.dist-info}/entry_points.txt +0 -0
  53. {khoj-1.16.1.dev15.dist-info → khoj-1.17.1.dev220.dist-info}/licenses/LICENSE +0 -0
khoj/configure.py CHANGED
@@ -42,7 +42,7 @@ from khoj.database.adapters import (
42
42
  )
43
43
  from khoj.database.models import ClientApplication, KhojUser, ProcessLock, Subscription
44
44
  from khoj.processor.embeddings import CrossEncoderModel, EmbeddingsModel
45
- from khoj.routers.indexer import configure_content, configure_search
45
+ from khoj.routers.api_content import configure_content, configure_search
46
46
  from khoj.routers.twilio import is_twilio_enabled
47
47
  from khoj.utils import constants, state
48
48
  from khoj.utils.config import SearchType
@@ -308,16 +308,16 @@ def configure_routes(app):
308
308
  from khoj.routers.api import api
309
309
  from khoj.routers.api_agents import api_agents
310
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
311
+ from khoj.routers.api_content import api_content
312
+ from khoj.routers.api_model import api_model
313
313
  from khoj.routers.notion import notion_router
314
314
  from khoj.routers.web_client import web_client
315
315
 
316
316
  app.include_router(api, prefix="/api")
317
317
  app.include_router(api_chat, prefix="/api/chat")
318
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")
319
+ app.include_router(api_model, prefix="/api/model")
320
+ app.include_router(api_content, prefix="/api/content")
321
321
  app.include_router(notion_router, prefix="/api/notion")
322
322
  app.include_router(web_client)
323
323
 
@@ -336,7 +336,7 @@ def configure_routes(app):
336
336
  if is_twilio_enabled():
337
337
  from khoj.routers.api_phone import api_phone
338
338
 
339
- app.include_router(api_phone, prefix="/api/config/phone")
339
+ app.include_router(api_phone, prefix="/api/phone")
340
340
  logger.info("📞 Enabled Twilio")
341
341
 
342
342
 
@@ -559,7 +559,7 @@ class AgentAdapters:
559
559
  if default_conversation_config is None:
560
560
  logger.info("No default conversation config found, skipping default agent creation")
561
561
  return None
562
- default_personality = prompts.personality.format(current_date="placeholder")
562
+ default_personality = prompts.personality.format(current_date="placeholder", day_of_week="placeholder")
563
563
 
564
564
  agent = Agent.objects.filter(name=AgentAdapters.DEFAULT_AGENT_NAME).first()
565
565
 
@@ -645,7 +645,11 @@ class ConversationAdapters:
645
645
 
646
646
  @staticmethod
647
647
  def get_conversation_sessions(user: KhojUser, client_application: ClientApplication = None):
648
- return Conversation.objects.filter(user=user, client=client_application).order_by("-updated_at")
648
+ return (
649
+ Conversation.objects.filter(user=user, client=client_application)
650
+ .prefetch_related("agent")
651
+ .order_by("-updated_at")
652
+ )
649
653
 
650
654
  @staticmethod
651
655
  async def aset_conversation_title(
@@ -680,19 +684,18 @@ class ConversationAdapters:
680
684
  async def aget_conversation_by_user(
681
685
  user: KhojUser, client_application: ClientApplication = None, conversation_id: int = None, title: str = None
682
686
  ) -> Optional[Conversation]:
687
+ query = Conversation.objects.filter(user=user, client=client_application).prefetch_related("agent")
688
+
683
689
  if conversation_id:
684
- return await Conversation.objects.filter(user=user, client=client_application, id=conversation_id).afirst()
690
+ return await query.filter(id=conversation_id).afirst()
685
691
  elif title:
686
- return await Conversation.objects.filter(user=user, client=client_application, title=title).afirst()
687
- else:
688
- conversation = Conversation.objects.filter(user=user, client=client_application).order_by("-updated_at")
692
+ return await query.filter(title=title).afirst()
689
693
 
690
- if await conversation.aexists():
691
- return await conversation.prefetch_related("agent").afirst()
694
+ conversation = await query.order_by("-updated_at").afirst()
692
695
 
693
- return await (
694
- Conversation.objects.filter(user=user, client=client_application).order_by("-updated_at").afirst()
695
- ) or await Conversation.objects.acreate(user=user, client=client_application)
696
+ return conversation or await Conversation.objects.prefetch_related("agent").acreate(
697
+ user=user, client=client_application
698
+ )
696
699
 
697
700
  @staticmethod
698
701
  async def adelete_conversation_by_user(
@@ -799,11 +802,14 @@ class ConversationAdapters:
799
802
  def create_conversation_from_public_conversation(
800
803
  user: KhojUser, public_conversation: PublicConversation, client_app: ClientApplication
801
804
  ):
805
+ scrubbed_title = public_conversation.title if public_conversation.title else public_conversation.slug
806
+ if scrubbed_title:
807
+ scrubbed_title = scrubbed_title.replace("-", " ")
802
808
  return Conversation.objects.create(
803
809
  user=user,
804
810
  conversation_log=public_conversation.conversation_log,
805
811
  client=client_app,
806
- slug=public_conversation.slug,
812
+ slug=scrubbed_title,
807
813
  title=public_conversation.title,
808
814
  agent=public_conversation.agent,
809
815
  )
@@ -944,6 +950,34 @@ class ConversationAdapters:
944
950
  )
945
951
  return new_config
946
952
 
953
+ @staticmethod
954
+ def add_files_to_filter(user: KhojUser, conversation_id: int, files: List[str]):
955
+ conversation = ConversationAdapters.get_conversation_by_user(user, conversation_id=conversation_id)
956
+ file_list = EntryAdapters.get_all_filenames_by_source(user, "computer")
957
+ for filename in files:
958
+ if filename in file_list and filename not in conversation.file_filters:
959
+ conversation.file_filters.append(filename)
960
+ conversation.save()
961
+
962
+ # remove files from conversation.file_filters that are not in file_list
963
+ conversation.file_filters = [file for file in conversation.file_filters if file in file_list]
964
+ conversation.save()
965
+ return conversation.file_filters
966
+
967
+ @staticmethod
968
+ def remove_files_from_filter(user: KhojUser, conversation_id: int, files: List[str]):
969
+ conversation = ConversationAdapters.get_conversation_by_user(user, conversation_id=conversation_id)
970
+ for filename in files:
971
+ if filename in conversation.file_filters:
972
+ conversation.file_filters.remove(filename)
973
+ conversation.save()
974
+
975
+ # remove files from conversation.file_filters that are not in file_list
976
+ file_list = EntryAdapters.get_all_filenames_by_source(user, "computer")
977
+ conversation.file_filters = [file for file in conversation.file_filters if file in file_list]
978
+ conversation.save()
979
+ return conversation.file_filters
980
+
947
981
 
948
982
  class FileObjectAdapters:
949
983
  @staticmethod
@@ -1072,6 +1106,16 @@ class EntryAdapters:
1072
1106
  async def adelete_entry_by_file(user: KhojUser, file_path: str):
1073
1107
  return await Entry.objects.filter(user=user, file_path=file_path).adelete()
1074
1108
 
1109
+ @staticmethod
1110
+ async def adelete_entries_by_filenames(user: KhojUser, filenames: List[str], batch_size=1000):
1111
+ deleted_count = 0
1112
+ for i in range(0, len(filenames), batch_size):
1113
+ batch = filenames[i : i + batch_size]
1114
+ count, _ = await Entry.objects.filter(user=user, file_path__in=batch).adelete()
1115
+ deleted_count += count
1116
+
1117
+ return deleted_count
1118
+
1075
1119
  @staticmethod
1076
1120
  def get_all_filenames_by_source(user: KhojUser, file_source: str):
1077
1121
  return (
@@ -0,0 +1,61 @@
1
+ # Generated by Django 5.0.6 on 2024-07-13 16:26
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("database", "0052_alter_searchmodelconfig_bi_encoder_docs_encode_config_and_more"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.AddField(
13
+ model_name="agent",
14
+ name="style_color",
15
+ field=models.CharField(
16
+ choices=[
17
+ ("blue", "Blue"),
18
+ ("green", "Green"),
19
+ ("red", "Red"),
20
+ ("yellow", "Yellow"),
21
+ ("orange", "Orange"),
22
+ ("purple", "Purple"),
23
+ ("pink", "Pink"),
24
+ ("teal", "Teal"),
25
+ ("cyan", "Cyan"),
26
+ ("lime", "Lime"),
27
+ ("indigo", "Indigo"),
28
+ ("fuschia", "Fuschia"),
29
+ ("rose", "Rose"),
30
+ ("sky", "Sky"),
31
+ ("amber", "Amber"),
32
+ ("emerald", "Emerald"),
33
+ ],
34
+ default="blue",
35
+ max_length=200,
36
+ ),
37
+ ),
38
+ migrations.AddField(
39
+ model_name="agent",
40
+ name="style_icon",
41
+ field=models.CharField(
42
+ choices=[
43
+ ("Lightbulb", "Lighbulb"),
44
+ ("Health", "Health"),
45
+ ("Robot", "Robot"),
46
+ ("Aperture", "Aperture"),
47
+ ("GraduationCap", "Graduation Cap"),
48
+ ("Jeep", "Jeep"),
49
+ ("Island", "Island"),
50
+ ("MathOperations", "Math Operations"),
51
+ ("Asclepius", "Asclepius"),
52
+ ("Couch", "Couch"),
53
+ ("Code", "Code"),
54
+ ("Atom", "Atom"),
55
+ ("ClockCounterClockwise", "Clock Counter Clockwise"),
56
+ ],
57
+ default="Lightbulb",
58
+ max_length=200,
59
+ ),
60
+ ),
61
+ ]
@@ -0,0 +1,38 @@
1
+ # Generated by Django 5.0.7 on 2024-08-05 06:19
2
+
3
+ from django.db import migrations, models
4
+
5
+
6
+ class Migration(migrations.Migration):
7
+ dependencies = [
8
+ ("database", "0053_agent_style_color_agent_style_icon"),
9
+ ]
10
+
11
+ operations = [
12
+ migrations.AlterField(
13
+ model_name="agent",
14
+ name="style_color",
15
+ field=models.CharField(
16
+ choices=[
17
+ ("blue", "Blue"),
18
+ ("green", "Green"),
19
+ ("red", "Red"),
20
+ ("yellow", "Yellow"),
21
+ ("orange", "Orange"),
22
+ ("purple", "Purple"),
23
+ ("pink", "Pink"),
24
+ ("teal", "Teal"),
25
+ ("cyan", "Cyan"),
26
+ ("lime", "Lime"),
27
+ ("indigo", "Indigo"),
28
+ ("fuchsia", "Fuchsia"),
29
+ ("rose", "Rose"),
30
+ ("sky", "Sky"),
31
+ ("amber", "Amber"),
32
+ ("emerald", "Emerald"),
33
+ ],
34
+ default="blue",
35
+ max_length=200,
36
+ ),
37
+ ),
38
+ ]
@@ -103,6 +103,39 @@ class VoiceModelOption(BaseModel):
103
103
 
104
104
 
105
105
  class Agent(BaseModel):
106
+ class StyleColorTypes(models.TextChoices):
107
+ BLUE = "blue"
108
+ GREEN = "green"
109
+ RED = "red"
110
+ YELLOW = "yellow"
111
+ ORANGE = "orange"
112
+ PURPLE = "purple"
113
+ PINK = "pink"
114
+ TEAL = "teal"
115
+ CYAN = "cyan"
116
+ LIME = "lime"
117
+ INDIGO = "indigo"
118
+ FUCHSIA = "fuchsia"
119
+ ROSE = "rose"
120
+ SKY = "sky"
121
+ AMBER = "amber"
122
+ EMERALD = "emerald"
123
+
124
+ class StyleIconTypes(models.TextChoices):
125
+ LIGHBULB = "Lightbulb"
126
+ HEALTH = "Health"
127
+ ROBOT = "Robot"
128
+ APERTURE = "Aperture"
129
+ GRADUATION_CAP = "GraduationCap"
130
+ JEEP = "Jeep"
131
+ ISLAND = "Island"
132
+ MATH_OPERATIONS = "MathOperations"
133
+ ASCLEPIUS = "Asclepius"
134
+ COUCH = "Couch"
135
+ CODE = "Code"
136
+ ATOM = "Atom"
137
+ CLOCK_COUNTER_CLOCKWISE = "ClockCounterClockwise"
138
+
106
139
  creator = models.ForeignKey(
107
140
  KhojUser, on_delete=models.CASCADE, default=None, null=True, blank=True
108
141
  ) # Creator will only be null when the agents are managed by admin
@@ -114,6 +147,8 @@ class Agent(BaseModel):
114
147
  managed_by_admin = models.BooleanField(default=False)
115
148
  chat_model = models.ForeignKey(ChatModelOptions, on_delete=models.CASCADE)
116
149
  slug = models.CharField(max_length=200)
150
+ style_color = models.CharField(max_length=200, choices=StyleColorTypes.choices, default=StyleColorTypes.BLUE)
151
+ style_icon = models.CharField(max_length=200, choices=StyleIconTypes.choices, default=StyleIconTypes.LIGHBULB)
117
152
 
118
153
 
119
154
  class ProcessLock(BaseModel):