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.
- khoj/__init__.py +0 -0
- khoj/app/README.md +94 -0
- khoj/app/__init__.py +0 -0
- khoj/app/asgi.py +16 -0
- khoj/app/settings.py +192 -0
- khoj/app/urls.py +25 -0
- khoj/configure.py +424 -0
- khoj/database/__init__.py +0 -0
- khoj/database/adapters/__init__.py +1234 -0
- khoj/database/admin.py +290 -0
- khoj/database/apps.py +6 -0
- khoj/database/management/__init__.py +0 -0
- khoj/database/management/commands/__init__.py +0 -0
- khoj/database/management/commands/change_generated_images_url.py +61 -0
- khoj/database/management/commands/convert_images_png_to_webp.py +99 -0
- khoj/database/migrations/0001_khojuser.py +98 -0
- khoj/database/migrations/0002_googleuser.py +32 -0
- khoj/database/migrations/0003_vector_extension.py +10 -0
- khoj/database/migrations/0004_content_types_and_more.py +181 -0
- khoj/database/migrations/0005_embeddings_corpus_id.py +19 -0
- khoj/database/migrations/0006_embeddingsdates.py +33 -0
- khoj/database/migrations/0007_add_conversation.py +27 -0
- khoj/database/migrations/0008_alter_conversation_conversation_log.py +17 -0
- khoj/database/migrations/0009_khojapiuser.py +24 -0
- khoj/database/migrations/0010_chatmodeloptions_and_more.py +83 -0
- khoj/database/migrations/0010_rename_embeddings_entry_and_more.py +30 -0
- khoj/database/migrations/0011_merge_20231102_0138.py +14 -0
- khoj/database/migrations/0012_entry_file_source.py +21 -0
- khoj/database/migrations/0013_subscription.py +37 -0
- khoj/database/migrations/0014_alter_googleuser_picture.py +17 -0
- khoj/database/migrations/0015_alter_subscription_user.py +21 -0
- khoj/database/migrations/0016_alter_subscription_renewal_date.py +17 -0
- khoj/database/migrations/0017_searchmodel.py +32 -0
- khoj/database/migrations/0018_searchmodelconfig_delete_searchmodel.py +30 -0
- khoj/database/migrations/0019_alter_googleuser_family_name_and_more.py +27 -0
- khoj/database/migrations/0020_reflectivequestion.py +36 -0
- khoj/database/migrations/0021_speechtotextmodeloptions_and_more.py +42 -0
- khoj/database/migrations/0022_texttoimagemodelconfig.py +25 -0
- khoj/database/migrations/0023_usersearchmodelconfig.py +33 -0
- khoj/database/migrations/0024_alter_entry_embeddings.py +18 -0
- khoj/database/migrations/0025_clientapplication_khojuser_phone_number_and_more.py +46 -0
- khoj/database/migrations/0025_searchmodelconfig_embeddings_inference_endpoint_and_more.py +22 -0
- khoj/database/migrations/0026_searchmodelconfig_cross_encoder_inference_endpoint_and_more.py +22 -0
- khoj/database/migrations/0027_merge_20240118_1324.py +13 -0
- khoj/database/migrations/0028_khojuser_verified_phone_number.py +17 -0
- khoj/database/migrations/0029_userrequests.py +27 -0
- khoj/database/migrations/0030_conversation_slug_and_title.py +38 -0
- khoj/database/migrations/0031_agent_conversation_agent.py +53 -0
- khoj/database/migrations/0031_alter_googleuser_locale.py +30 -0
- khoj/database/migrations/0032_merge_20240322_0427.py +14 -0
- khoj/database/migrations/0033_rename_tuning_agent_personality.py +17 -0
- khoj/database/migrations/0034_alter_chatmodeloptions_chat_model.py +32 -0
- khoj/database/migrations/0035_processlock.py +26 -0
- khoj/database/migrations/0036_alter_processlock_name.py +19 -0
- khoj/database/migrations/0036_delete_offlinechatprocessorconversationconfig.py +15 -0
- khoj/database/migrations/0036_publicconversation.py +42 -0
- khoj/database/migrations/0037_chatmodeloptions_openai_config_and_more.py +51 -0
- khoj/database/migrations/0037_searchmodelconfig_bi_encoder_docs_encode_config_and_more.py +32 -0
- khoj/database/migrations/0038_merge_20240425_0857.py +14 -0
- khoj/database/migrations/0038_merge_20240426_1640.py +12 -0
- khoj/database/migrations/0039_merge_20240501_0301.py +12 -0
- khoj/database/migrations/0040_alter_processlock_name.py +26 -0
- khoj/database/migrations/0040_merge_20240504_1010.py +14 -0
- khoj/database/migrations/0041_merge_20240505_1234.py +14 -0
- khoj/database/migrations/0042_serverchatsettings.py +46 -0
- khoj/database/migrations/0043_alter_chatmodeloptions_model_type.py +21 -0
- khoj/database/migrations/0044_conversation_file_filters.py +17 -0
- khoj/database/migrations/0045_fileobject.py +37 -0
- khoj/database/migrations/0046_khojuser_email_verification_code_and_more.py +22 -0
- khoj/database/migrations/0047_alter_entry_file_type.py +31 -0
- khoj/database/migrations/0048_voicemodeloption_uservoicemodelconfig.py +52 -0
- khoj/database/migrations/0049_datastore.py +38 -0
- khoj/database/migrations/0049_texttoimagemodelconfig_api_key_and_more.py +58 -0
- khoj/database/migrations/0050_alter_processlock_name.py +25 -0
- khoj/database/migrations/0051_merge_20240702_1220.py +14 -0
- khoj/database/migrations/0052_alter_searchmodelconfig_bi_encoder_docs_encode_config_and_more.py +27 -0
- khoj/database/migrations/__init__.py +0 -0
- khoj/database/models/__init__.py +402 -0
- khoj/database/tests.py +3 -0
- khoj/interface/email/feedback.html +34 -0
- khoj/interface/email/magic_link.html +17 -0
- khoj/interface/email/task.html +40 -0
- khoj/interface/email/welcome.html +61 -0
- khoj/interface/web/404.html +56 -0
- khoj/interface/web/agent.html +312 -0
- khoj/interface/web/agents.html +276 -0
- khoj/interface/web/assets/icons/agents.svg +6 -0
- khoj/interface/web/assets/icons/automation.svg +37 -0
- khoj/interface/web/assets/icons/cancel.svg +3 -0
- khoj/interface/web/assets/icons/chat.svg +24 -0
- khoj/interface/web/assets/icons/collapse.svg +17 -0
- khoj/interface/web/assets/icons/computer.png +0 -0
- khoj/interface/web/assets/icons/confirm-icon.svg +1 -0
- khoj/interface/web/assets/icons/copy-button-success.svg +6 -0
- khoj/interface/web/assets/icons/copy-button.svg +5 -0
- khoj/interface/web/assets/icons/credit-card.png +0 -0
- khoj/interface/web/assets/icons/delete.svg +26 -0
- khoj/interface/web/assets/icons/docx.svg +7 -0
- khoj/interface/web/assets/icons/edit.svg +4 -0
- khoj/interface/web/assets/icons/favicon-128x128.ico +0 -0
- khoj/interface/web/assets/icons/favicon-128x128.png +0 -0
- khoj/interface/web/assets/icons/favicon-256x256.png +0 -0
- khoj/interface/web/assets/icons/favicon.icns +0 -0
- khoj/interface/web/assets/icons/github.svg +1 -0
- khoj/interface/web/assets/icons/key.svg +4 -0
- khoj/interface/web/assets/icons/khoj-logo-sideways-200.png +0 -0
- khoj/interface/web/assets/icons/khoj-logo-sideways-500.png +0 -0
- khoj/interface/web/assets/icons/khoj-logo-sideways.svg +5385 -0
- khoj/interface/web/assets/icons/logotype.svg +1 -0
- khoj/interface/web/assets/icons/markdown.svg +1 -0
- khoj/interface/web/assets/icons/new.svg +23 -0
- khoj/interface/web/assets/icons/notion.svg +4 -0
- khoj/interface/web/assets/icons/openai-logomark.svg +1 -0
- khoj/interface/web/assets/icons/org.svg +1 -0
- khoj/interface/web/assets/icons/pdf.svg +23 -0
- khoj/interface/web/assets/icons/pencil-edit.svg +5 -0
- khoj/interface/web/assets/icons/plaintext.svg +1 -0
- khoj/interface/web/assets/icons/question-mark-icon.svg +1 -0
- khoj/interface/web/assets/icons/search.svg +25 -0
- khoj/interface/web/assets/icons/send.svg +1 -0
- khoj/interface/web/assets/icons/share.svg +8 -0
- khoj/interface/web/assets/icons/speaker.svg +4 -0
- khoj/interface/web/assets/icons/stop-solid.svg +37 -0
- khoj/interface/web/assets/icons/sync.svg +4 -0
- khoj/interface/web/assets/icons/thumbs-down-svgrepo-com.svg +6 -0
- khoj/interface/web/assets/icons/thumbs-up-svgrepo-com.svg +6 -0
- khoj/interface/web/assets/icons/user-silhouette.svg +4 -0
- khoj/interface/web/assets/icons/voice.svg +8 -0
- khoj/interface/web/assets/icons/web.svg +2 -0
- khoj/interface/web/assets/icons/whatsapp.svg +17 -0
- khoj/interface/web/assets/khoj.css +237 -0
- khoj/interface/web/assets/markdown-it.min.js +8476 -0
- khoj/interface/web/assets/natural-cron.min.js +1 -0
- khoj/interface/web/assets/org.min.js +1823 -0
- khoj/interface/web/assets/pico.min.css +5 -0
- khoj/interface/web/assets/purify.min.js +3 -0
- khoj/interface/web/assets/samples/desktop-browse-draw-sample.png +0 -0
- khoj/interface/web/assets/samples/desktop-plain-chat-sample.png +0 -0
- khoj/interface/web/assets/samples/desktop-remember-plan-sample.png +0 -0
- khoj/interface/web/assets/samples/phone-browse-draw-sample.png +0 -0
- khoj/interface/web/assets/samples/phone-plain-chat-sample.png +0 -0
- khoj/interface/web/assets/samples/phone-remember-plan-sample.png +0 -0
- khoj/interface/web/assets/utils.js +33 -0
- khoj/interface/web/base_config.html +445 -0
- khoj/interface/web/chat.html +3546 -0
- khoj/interface/web/config.html +1011 -0
- khoj/interface/web/config_automation.html +1103 -0
- khoj/interface/web/content_source_computer_input.html +139 -0
- khoj/interface/web/content_source_github_input.html +216 -0
- khoj/interface/web/content_source_notion_input.html +94 -0
- khoj/interface/web/khoj.webmanifest +51 -0
- khoj/interface/web/login.html +219 -0
- khoj/interface/web/public_conversation.html +2006 -0
- khoj/interface/web/search.html +470 -0
- khoj/interface/web/utils.html +48 -0
- khoj/main.py +241 -0
- khoj/manage.py +22 -0
- khoj/migrations/__init__.py +0 -0
- khoj/migrations/migrate_offline_chat_default_model.py +69 -0
- khoj/migrations/migrate_offline_chat_default_model_2.py +71 -0
- khoj/migrations/migrate_offline_chat_schema.py +83 -0
- khoj/migrations/migrate_offline_model.py +29 -0
- khoj/migrations/migrate_processor_config_openai.py +67 -0
- khoj/migrations/migrate_server_pg.py +138 -0
- khoj/migrations/migrate_version.py +17 -0
- khoj/processor/__init__.py +0 -0
- khoj/processor/content/__init__.py +0 -0
- khoj/processor/content/docx/__init__.py +0 -0
- khoj/processor/content/docx/docx_to_entries.py +110 -0
- khoj/processor/content/github/__init__.py +0 -0
- khoj/processor/content/github/github_to_entries.py +224 -0
- khoj/processor/content/images/__init__.py +0 -0
- khoj/processor/content/images/image_to_entries.py +118 -0
- khoj/processor/content/markdown/__init__.py +0 -0
- khoj/processor/content/markdown/markdown_to_entries.py +165 -0
- khoj/processor/content/notion/notion_to_entries.py +260 -0
- khoj/processor/content/org_mode/__init__.py +0 -0
- khoj/processor/content/org_mode/org_to_entries.py +231 -0
- khoj/processor/content/org_mode/orgnode.py +532 -0
- khoj/processor/content/pdf/__init__.py +0 -0
- khoj/processor/content/pdf/pdf_to_entries.py +116 -0
- khoj/processor/content/plaintext/__init__.py +0 -0
- khoj/processor/content/plaintext/plaintext_to_entries.py +122 -0
- khoj/processor/content/text_to_entries.py +297 -0
- khoj/processor/conversation/__init__.py +0 -0
- khoj/processor/conversation/anthropic/__init__.py +0 -0
- khoj/processor/conversation/anthropic/anthropic_chat.py +206 -0
- khoj/processor/conversation/anthropic/utils.py +114 -0
- khoj/processor/conversation/offline/__init__.py +0 -0
- khoj/processor/conversation/offline/chat_model.py +231 -0
- khoj/processor/conversation/offline/utils.py +78 -0
- khoj/processor/conversation/offline/whisper.py +15 -0
- khoj/processor/conversation/openai/__init__.py +0 -0
- khoj/processor/conversation/openai/gpt.py +187 -0
- khoj/processor/conversation/openai/utils.py +129 -0
- khoj/processor/conversation/openai/whisper.py +13 -0
- khoj/processor/conversation/prompts.py +758 -0
- khoj/processor/conversation/utils.py +262 -0
- khoj/processor/embeddings.py +117 -0
- khoj/processor/speech/__init__.py +0 -0
- khoj/processor/speech/text_to_speech.py +51 -0
- khoj/processor/tools/__init__.py +0 -0
- khoj/processor/tools/online_search.py +225 -0
- khoj/routers/__init__.py +0 -0
- khoj/routers/api.py +626 -0
- khoj/routers/api_agents.py +43 -0
- khoj/routers/api_chat.py +1180 -0
- khoj/routers/api_config.py +434 -0
- khoj/routers/api_phone.py +86 -0
- khoj/routers/auth.py +181 -0
- khoj/routers/email.py +133 -0
- khoj/routers/helpers.py +1188 -0
- khoj/routers/indexer.py +349 -0
- khoj/routers/notion.py +91 -0
- khoj/routers/storage.py +35 -0
- khoj/routers/subscription.py +104 -0
- khoj/routers/twilio.py +36 -0
- khoj/routers/web_client.py +471 -0
- khoj/search_filter/__init__.py +0 -0
- khoj/search_filter/base_filter.py +15 -0
- khoj/search_filter/date_filter.py +217 -0
- khoj/search_filter/file_filter.py +30 -0
- khoj/search_filter/word_filter.py +29 -0
- khoj/search_type/__init__.py +0 -0
- khoj/search_type/text_search.py +241 -0
- khoj/utils/__init__.py +0 -0
- khoj/utils/cli.py +93 -0
- khoj/utils/config.py +81 -0
- khoj/utils/constants.py +24 -0
- khoj/utils/fs_syncer.py +249 -0
- khoj/utils/helpers.py +418 -0
- khoj/utils/initialization.py +146 -0
- khoj/utils/jsonl.py +43 -0
- khoj/utils/models.py +47 -0
- khoj/utils/rawconfig.py +160 -0
- khoj/utils/state.py +46 -0
- khoj/utils/yaml.py +43 -0
- khoj-1.16.1.dev15.dist-info/METADATA +178 -0
- khoj-1.16.1.dev15.dist-info/RECORD +242 -0
- khoj-1.16.1.dev15.dist-info/WHEEL +4 -0
- khoj-1.16.1.dev15.dist-info/entry_points.txt +2 -0
- 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
|
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
|
+
]
|