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/database/admin.py ADDED
@@ -0,0 +1,290 @@
1
+ import csv
2
+ import json
3
+
4
+ from apscheduler.job import Job
5
+ from django.contrib import admin, messages
6
+ from django.contrib.auth.admin import UserAdmin
7
+ from django.http import HttpResponse
8
+ from django_apscheduler.admin import DjangoJobAdmin
9
+ from django_apscheduler.jobstores import DjangoJobStore
10
+ from django_apscheduler.models import DjangoJob
11
+
12
+ from khoj.database.models import (
13
+ Agent,
14
+ ChatModelOptions,
15
+ ClientApplication,
16
+ Conversation,
17
+ Entry,
18
+ GithubConfig,
19
+ KhojUser,
20
+ NotionConfig,
21
+ OpenAIProcessorConversationConfig,
22
+ ProcessLock,
23
+ ReflectiveQuestion,
24
+ SearchModelConfig,
25
+ ServerChatSettings,
26
+ SpeechToTextModelOptions,
27
+ Subscription,
28
+ TextToImageModelConfig,
29
+ UserSearchModelConfig,
30
+ VoiceModelOption,
31
+ )
32
+ from khoj.utils.helpers import ImageIntentType
33
+
34
+ admin.site.unregister(DjangoJob)
35
+
36
+
37
+ class KhojDjangoJobAdmin(DjangoJobAdmin):
38
+ list_display = (
39
+ "id",
40
+ "next_run_time",
41
+ "job_info",
42
+ )
43
+ search_fields = ("id", "next_run_time")
44
+ ordering = ("-next_run_time",)
45
+ job_store = DjangoJobStore()
46
+
47
+ def job_info(self, obj):
48
+ job: Job = self.job_store.lookup_job(obj.id)
49
+ return f"{job.func_ref} {job.args} {job.kwargs}" if job else "None"
50
+
51
+ job_info.short_description = "Job Info" # type: ignore
52
+
53
+ def get_search_results(self, request, queryset, search_term):
54
+ queryset, use_distinct = super().get_search_results(request, queryset, search_term)
55
+ if search_term:
56
+ jobs = [job.id for job in self.job_store.get_all_jobs() if search_term in str(job)]
57
+ queryset |= self.model.objects.filter(id__in=jobs)
58
+ return queryset, use_distinct
59
+
60
+
61
+ admin.site.register(DjangoJob, KhojDjangoJobAdmin)
62
+
63
+
64
+ class KhojUserAdmin(UserAdmin):
65
+ list_display = (
66
+ "id",
67
+ "email",
68
+ "username",
69
+ "is_active",
70
+ "is_staff",
71
+ "is_superuser",
72
+ "phone_number",
73
+ )
74
+ search_fields = ("email", "username", "phone_number", "uuid")
75
+ filter_horizontal = ("groups", "user_permissions")
76
+
77
+ fieldsets = (("Personal info", {"fields": ("phone_number", "email_verification_code")}),) + UserAdmin.fieldsets
78
+
79
+ actions = ["get_email_login_url"]
80
+
81
+ def get_email_login_url(self, request, queryset):
82
+ for user in queryset:
83
+ if user.email:
84
+ host = request.get_host()
85
+ unique_id = user.email_verification_code
86
+ login_url = f"{host}/auth/magic?code={unique_id}"
87
+ messages.info(request, f"Email login URL for {user.email}: {login_url}")
88
+
89
+ get_email_login_url.short_description = "Get email login URL" # type: ignore
90
+
91
+
92
+ admin.site.register(KhojUser, KhojUserAdmin)
93
+
94
+ admin.site.register(ProcessLock)
95
+ admin.site.register(SpeechToTextModelOptions)
96
+ admin.site.register(SearchModelConfig)
97
+ admin.site.register(ReflectiveQuestion)
98
+ admin.site.register(UserSearchModelConfig)
99
+ admin.site.register(ClientApplication)
100
+ admin.site.register(GithubConfig)
101
+ admin.site.register(NotionConfig)
102
+ admin.site.register(VoiceModelOption)
103
+
104
+
105
+ @admin.register(Agent)
106
+ class AgentAdmin(admin.ModelAdmin):
107
+ list_display = (
108
+ "id",
109
+ "name",
110
+ )
111
+ search_fields = ("id", "name")
112
+ ordering = ("-created_at",)
113
+
114
+
115
+ @admin.register(Entry)
116
+ class EntryAdmin(admin.ModelAdmin):
117
+ list_display = (
118
+ "id",
119
+ "created_at",
120
+ "updated_at",
121
+ "user",
122
+ "file_source",
123
+ "file_type",
124
+ "file_name",
125
+ "file_path",
126
+ )
127
+ search_fields = ("id", "user__email", "user__username", "file_path")
128
+ list_filter = (
129
+ "file_type",
130
+ "user__email",
131
+ )
132
+ ordering = ("-created_at",)
133
+
134
+
135
+ @admin.register(Subscription)
136
+ class KhojUserSubscription(admin.ModelAdmin):
137
+ list_display = (
138
+ "id",
139
+ "user",
140
+ "type",
141
+ )
142
+
143
+ search_fields = ("id", "user__email", "user__username", "type")
144
+ list_filter = ("type",)
145
+
146
+
147
+ @admin.register(ChatModelOptions)
148
+ class ChatModelOptionsAdmin(admin.ModelAdmin):
149
+ list_display = (
150
+ "id",
151
+ "chat_model",
152
+ "model_type",
153
+ "max_prompt_size",
154
+ )
155
+ search_fields = ("id", "chat_model", "model_type")
156
+
157
+
158
+ @admin.register(TextToImageModelConfig)
159
+ class TextToImageModelOptionsAdmin(admin.ModelAdmin):
160
+ list_display = (
161
+ "id",
162
+ "model_name",
163
+ "model_type",
164
+ )
165
+ search_fields = ("id", "model_name", "model_type")
166
+
167
+
168
+ @admin.register(OpenAIProcessorConversationConfig)
169
+ class OpenAIProcessorConversationConfigAdmin(admin.ModelAdmin):
170
+ list_display = (
171
+ "id",
172
+ "name",
173
+ "api_key",
174
+ "api_base_url",
175
+ )
176
+ search_fields = ("id", "name", "api_key", "api_base_url")
177
+
178
+
179
+ @admin.register(ServerChatSettings)
180
+ class ServerChatSettingsAdmin(admin.ModelAdmin):
181
+ list_display = (
182
+ "default_model",
183
+ "summarizer_model",
184
+ )
185
+
186
+
187
+ @admin.register(Conversation)
188
+ class ConversationAdmin(admin.ModelAdmin):
189
+ list_display = (
190
+ "id",
191
+ "user",
192
+ "created_at",
193
+ "updated_at",
194
+ "client",
195
+ )
196
+ search_fields = ("id", "user__email", "user__username", "client__name")
197
+ list_filter = ("agent",)
198
+ ordering = ("-created_at",)
199
+
200
+ actions = ["export_selected_objects", "export_selected_minimal_objects"]
201
+
202
+ def export_selected_objects(self, request, queryset):
203
+ response = HttpResponse(content_type="text/csv")
204
+ response["Content-Disposition"] = 'attachment; filename="conversations.csv"'
205
+
206
+ writer = csv.writer(response)
207
+ writer.writerow(["id", "user", "created_at", "updated_at", "conversation_log"])
208
+
209
+ for conversation in queryset:
210
+ modified_log = conversation.conversation_log
211
+ chat_log = modified_log.get("chat", [])
212
+ for idx, log in enumerate(chat_log):
213
+ if (
214
+ log["by"] == "khoj"
215
+ and log["intent"]
216
+ and log["intent"]["type"]
217
+ and (
218
+ log["intent"]["type"] == ImageIntentType.TEXT_TO_IMAGE.value
219
+ or log["intent"]["type"] == ImageIntentType.TEXT_TO_IMAGE_V3.value
220
+ )
221
+ ):
222
+ log["message"] = "inline image redacted for space"
223
+ chat_log[idx] = log
224
+ modified_log["chat"] = chat_log
225
+
226
+ writer.writerow(
227
+ [
228
+ conversation.id,
229
+ conversation.user,
230
+ conversation.created_at,
231
+ conversation.updated_at,
232
+ json.dumps(modified_log),
233
+ ]
234
+ )
235
+
236
+ return response
237
+
238
+ export_selected_objects.short_description = "Export selected conversations" # type: ignore
239
+
240
+ def export_selected_minimal_objects(self, request, queryset):
241
+ response = HttpResponse(content_type="text/csv")
242
+ response["Content-Disposition"] = 'attachment; filename="conversations.csv"'
243
+
244
+ writer = csv.writer(response)
245
+ writer.writerow(["id", "user", "created_at", "updated_at", "conversation_log"])
246
+
247
+ fields_to_keep = set(["message", "by", "created"])
248
+
249
+ for conversation in queryset:
250
+ return_log = dict()
251
+ chat_log = conversation.conversation_log.get("chat", [])
252
+ for idx, log in enumerate(chat_log):
253
+ updated_log = {}
254
+ for key in fields_to_keep:
255
+ updated_log[key] = log[key]
256
+ if (
257
+ log["by"] == "khoj"
258
+ and log["intent"]
259
+ and log["intent"]["type"]
260
+ and (
261
+ log["intent"]["type"] == ImageIntentType.TEXT_TO_IMAGE.value
262
+ or log["intent"]["type"] == ImageIntentType.TEXT_TO_IMAGE_V3.value
263
+ )
264
+ ):
265
+ updated_log["message"] = "inline image redacted for space"
266
+ chat_log[idx] = updated_log
267
+ return_log["chat"] = chat_log
268
+
269
+ writer.writerow(
270
+ [
271
+ conversation.id,
272
+ conversation.user,
273
+ conversation.created_at,
274
+ conversation.updated_at,
275
+ json.dumps(return_log),
276
+ ]
277
+ )
278
+
279
+ return response
280
+
281
+ export_selected_minimal_objects.short_description = "Export selected conversations (minimal)" # type: ignore
282
+
283
+ def get_actions(self, request):
284
+ actions = super().get_actions(request)
285
+ if not request.user.is_superuser:
286
+ if "export_selected_objects" in actions:
287
+ del actions["export_selected_objects"]
288
+ if "export_selected_minimal_objects" in actions:
289
+ del actions["export_selected_minimal_objects"]
290
+ return actions
khoj/database/apps.py ADDED
@@ -0,0 +1,6 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class DatabaseConfig(AppConfig):
5
+ default_auto_field = "django.db.models.BigAutoField"
6
+ name = "khoj.database"
File without changes
File without changes
@@ -0,0 +1,61 @@
1
+ from django.core.management.base import BaseCommand
2
+ from tqdm import tqdm
3
+
4
+ from khoj.database.models import Conversation
5
+ from khoj.utils.helpers import ImageIntentType, is_none_or_empty
6
+
7
+
8
+ class Command(BaseCommand):
9
+ help = "Serve Khoj generated images from a different URL."
10
+
11
+ def add_arguments(self, parser):
12
+ # Pass Source URL
13
+ parser.add_argument(
14
+ "--source",
15
+ action="store",
16
+ help="URL from which generated images are currently served.",
17
+ )
18
+ # Pass Destination URL
19
+ parser.add_argument("--destination", action="store", help="URL to serve generated image from going forward.")
20
+
21
+ # Add a new argument 'reverse' to the command
22
+ parser.add_argument(
23
+ "--reverse",
24
+ action="store_true",
25
+ help="Revert to serve generated images from source instead of destination URL.",
26
+ )
27
+
28
+ def handle(self, *args, **options):
29
+ updated_count = 0
30
+ if not options.get("source") or not options.get("destination"):
31
+ self.stdout.write(
32
+ self.style.ERROR(
33
+ "Set --source, --destination args to migrate serving images from source to destination URL."
34
+ )
35
+ )
36
+ return
37
+
38
+ destination = options["source"] if options["reverse"] else options["destination"]
39
+ source = options["destination"] if options["reverse"] else options["source"]
40
+ for conversation in Conversation.objects.all():
41
+ conversation_updated = False
42
+ for chat in tqdm(conversation.conversation_log.get("chat", []), desc="Processing Conversations"):
43
+ if (
44
+ chat.get("by", "") == "khoj"
45
+ and not is_none_or_empty(chat.get("message"))
46
+ and chat.get("message", "").startswith(source)
47
+ and chat.get("intent", {}).get("type", "") == ImageIntentType.TEXT_TO_IMAGE2.value
48
+ and chat.get("message", "").endswith(".webp")
49
+ ):
50
+ # Convert source url to destination url
51
+ chat["message"] = chat["message"].replace(source, destination)
52
+ conversation_updated = True
53
+ updated_count += 1
54
+
55
+ if conversation_updated:
56
+ print(f"Save the updated conversation {conversation.id} to the database.")
57
+ conversation.save()
58
+
59
+ if updated_count > 0:
60
+ success = f"Successfully converted {updated_count} image URLs from {source} to {destination}.".strip()
61
+ self.stdout.write(self.style.SUCCESS(success))
@@ -0,0 +1,99 @@
1
+ import base64
2
+ import io
3
+
4
+ from django.core.management.base import BaseCommand
5
+ from PIL import Image
6
+
7
+ from khoj.database.models import Conversation
8
+ from khoj.utils.helpers import ImageIntentType
9
+
10
+
11
+ class Command(BaseCommand):
12
+ help = "Convert all images to WebP format or reverse."
13
+
14
+ def add_arguments(self, parser):
15
+ # Add a new argument 'reverse' to the command
16
+ parser.add_argument(
17
+ "--reverse",
18
+ action="store_true",
19
+ help="Convert from WebP to PNG instead of PNG to WebP",
20
+ )
21
+
22
+ def handle(self, *args, **options):
23
+ updated_count = 0
24
+ for conversation in Conversation.objects.all():
25
+ conversation_updated = False
26
+ for chat in conversation.conversation_log.get("chat", []):
27
+ if (
28
+ chat.get("by", "") == "khoj"
29
+ and chat.get("intent", {}).get("type", "") == ImageIntentType.TEXT_TO_IMAGE.value
30
+ and not options["reverse"]
31
+ ):
32
+ # Decode the base64 encoded PNG image
33
+ print("Decode the base64 encoded PNG image")
34
+ decoded_image = base64.b64decode(chat["message"])
35
+
36
+ # Convert images from PNG to WebP format
37
+ print("Convert images from PNG to WebP format")
38
+ image_io = io.BytesIO(decoded_image)
39
+ with Image.open(image_io) as png_image:
40
+ webp_image_io = io.BytesIO()
41
+ png_image.save(webp_image_io, "WEBP")
42
+
43
+ # Encode the WebP image back to base64
44
+ webp_image_bytes = webp_image_io.getvalue()
45
+ chat["message"] = base64.b64encode(webp_image_bytes).decode()
46
+ chat["intent"]["type"] = ImageIntentType.TEXT_TO_IMAGE_V3.value
47
+ webp_image_io.close()
48
+ conversation_updated = True
49
+ updated_count += 1
50
+
51
+ elif (
52
+ chat.get("by", "") == "khoj"
53
+ and chat.get("intent", {}).get("type", "") == ImageIntentType.TEXT_TO_IMAGE_V3.value
54
+ and options["reverse"]
55
+ ):
56
+ # Decode the base64 encoded WebP image
57
+ print("Decode the base64 encoded WebP image")
58
+ decoded_image = base64.b64decode(chat["message"])
59
+
60
+ # Convert images from WebP to PNG format
61
+ print("Convert images from WebP to PNG format")
62
+ image_io = io.BytesIO(decoded_image)
63
+ with Image.open(image_io) as png_image:
64
+ webp_image_io = io.BytesIO()
65
+ png_image.save(webp_image_io, "PNG")
66
+
67
+ # Encode the WebP image back to base64
68
+ webp_image_bytes = webp_image_io.getvalue()
69
+ chat["message"] = base64.b64encode(webp_image_bytes).decode()
70
+ chat["intent"]["type"] = ImageIntentType.TEXT_TO_IMAGE.value
71
+ webp_image_io.close()
72
+ conversation_updated = True
73
+ updated_count += 1
74
+
75
+ elif (
76
+ chat.get("by", "") == "khoj"
77
+ and chat.get("intent", {}).get("type", "") == ImageIntentType.TEXT_TO_IMAGE2.value
78
+ ):
79
+ if options["reverse"] and chat.get("message", "").endswith(".webp"):
80
+ # Convert WebP url to PNG url
81
+ print("Convert WebP url to PNG url")
82
+ chat["message"] = chat["message"].replace(".webp", ".png")
83
+ conversation_updated = True
84
+ updated_count += 1
85
+ elif chat.get("message", "").endswith(".png"):
86
+ # Convert PNG url to WebP url
87
+ print("Convert PNG url to WebP url")
88
+ chat["message"] = chat["message"].replace(".png", ".webp")
89
+ conversation_updated = True
90
+ updated_count += 1
91
+
92
+ if conversation_updated:
93
+ print("Save the updated conversation")
94
+ conversation.save()
95
+
96
+ if updated_count > 0 and options["reverse"]:
97
+ self.stdout.write(self.style.SUCCESS(f"Successfully converted {updated_count} WebP images to PNG format."))
98
+ elif updated_count > 0:
99
+ self.stdout.write(self.style.SUCCESS(f"Successfully converted {updated_count} PNG images to WebP format."))
@@ -0,0 +1,98 @@
1
+ # Generated by Django 4.2.5 on 2023-09-14 19:00
2
+
3
+ import django.contrib.auth.models
4
+ import django.contrib.auth.validators
5
+ import django.utils.timezone
6
+ from django.db import migrations, models
7
+
8
+
9
+ class Migration(migrations.Migration):
10
+ initial = True
11
+
12
+ dependencies = [
13
+ ("auth", "0012_alter_user_first_name_max_length"),
14
+ ]
15
+
16
+ run_before = [
17
+ ("admin", "0001_initial"),
18
+ ]
19
+
20
+ operations = [
21
+ migrations.CreateModel(
22
+ name="KhojUser",
23
+ fields=[
24
+ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
25
+ ("password", models.CharField(max_length=128, verbose_name="password")),
26
+ ("last_login", models.DateTimeField(blank=True, null=True, verbose_name="last login")),
27
+ (
28
+ "is_superuser",
29
+ models.BooleanField(
30
+ default=False,
31
+ help_text="Designates that this user has all permissions without explicitly assigning them.",
32
+ verbose_name="superuser status",
33
+ ),
34
+ ),
35
+ (
36
+ "username",
37
+ models.CharField(
38
+ error_messages={"unique": "A user with that username already exists."},
39
+ help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
40
+ max_length=150,
41
+ unique=True,
42
+ validators=[django.contrib.auth.validators.UnicodeUsernameValidator()],
43
+ verbose_name="username",
44
+ ),
45
+ ),
46
+ ("first_name", models.CharField(blank=True, max_length=150, verbose_name="first name")),
47
+ ("last_name", models.CharField(blank=True, max_length=150, verbose_name="last name")),
48
+ ("email", models.EmailField(blank=True, max_length=254, verbose_name="email address")),
49
+ (
50
+ "is_staff",
51
+ models.BooleanField(
52
+ default=False,
53
+ help_text="Designates whether the user can log into this admin site.",
54
+ verbose_name="staff status",
55
+ ),
56
+ ),
57
+ (
58
+ "is_active",
59
+ models.BooleanField(
60
+ default=True,
61
+ help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
62
+ verbose_name="active",
63
+ ),
64
+ ),
65
+ ("date_joined", models.DateTimeField(default=django.utils.timezone.now, verbose_name="date joined")),
66
+ (
67
+ "groups",
68
+ models.ManyToManyField(
69
+ blank=True,
70
+ help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.",
71
+ related_name="user_set",
72
+ related_query_name="user",
73
+ to="auth.group",
74
+ verbose_name="groups",
75
+ ),
76
+ ),
77
+ (
78
+ "user_permissions",
79
+ models.ManyToManyField(
80
+ blank=True,
81
+ help_text="Specific permissions for this user.",
82
+ related_name="user_set",
83
+ related_query_name="user",
84
+ to="auth.permission",
85
+ verbose_name="user permissions",
86
+ ),
87
+ ),
88
+ ],
89
+ options={
90
+ "verbose_name": "user",
91
+ "verbose_name_plural": "users",
92
+ "abstract": False,
93
+ },
94
+ managers=[
95
+ ("objects", django.contrib.auth.models.UserManager()),
96
+ ],
97
+ ),
98
+ ]
@@ -0,0 +1,32 @@
1
+ # Generated by Django 4.2.4 on 2023-09-18 23:24
2
+
3
+ import django.db.models.deletion
4
+ from django.conf import settings
5
+ from django.db import migrations, models
6
+
7
+
8
+ class Migration(migrations.Migration):
9
+ dependencies = [
10
+ ("database", "0001_khojuser"),
11
+ ]
12
+
13
+ operations = [
14
+ migrations.CreateModel(
15
+ name="GoogleUser",
16
+ fields=[
17
+ ("id", models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
18
+ ("sub", models.CharField(max_length=200)),
19
+ ("azp", models.CharField(max_length=200)),
20
+ ("email", models.CharField(max_length=200)),
21
+ ("name", models.CharField(max_length=200)),
22
+ ("given_name", models.CharField(max_length=200)),
23
+ ("family_name", models.CharField(max_length=200)),
24
+ ("picture", models.CharField(max_length=200)),
25
+ ("locale", models.CharField(max_length=200)),
26
+ (
27
+ "user",
28
+ models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
29
+ ),
30
+ ],
31
+ ),
32
+ ]
@@ -0,0 +1,10 @@
1
+ from django.db import migrations
2
+ from pgvector.django import VectorExtension
3
+
4
+
5
+ class Migration(migrations.Migration):
6
+ dependencies = [
7
+ ("database", "0002_googleuser"),
8
+ ]
9
+
10
+ operations = [VectorExtension()]