khoj 1.28.3__py3-none-any.whl → 1.28.4.dev92__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/configure.py +10 -14
- khoj/database/adapters/__init__.py +128 -44
- khoj/database/admin.py +6 -3
- khoj/database/management/commands/change_default_model.py +7 -72
- khoj/database/migrations/0073_delete_usersearchmodelconfig.py +15 -0
- khoj/database/models/__init__.py +4 -6
- khoj/interface/compiled/404/index.html +1 -1
- khoj/interface/compiled/_next/static/chunks/1603-dc5fd983dbcd070d.js +1 -0
- khoj/interface/compiled/_next/static/chunks/1970-c78f6acc8e16e30b.js +1 -0
- khoj/interface/compiled/_next/static/chunks/2261-748f7c327df3c8c1.js +1 -0
- khoj/interface/compiled/_next/static/chunks/3124-a4cea2eda163128d.js +1 -0
- khoj/interface/compiled/_next/static/chunks/3803-d74118a2d0182c52.js +1 -0
- khoj/interface/compiled/_next/static/chunks/5538-36aa824a75519c5b.js +1 -0
- khoj/interface/compiled/_next/static/chunks/5961-3c104d9736b7902b.js +3 -0
- khoj/interface/compiled/_next/static/chunks/8423-ebfa9bb9e2424ca3.js +1 -0
- khoj/interface/compiled/_next/static/chunks/9417-32c4db52ca42e681.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/agents/layout-e9838b642913a071.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/agents/page-4353b1a532795ad1.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/automations/{page-d3edae545a1b5393.js → page-c9f13c865e739607.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/chat/layout-b0e7ff4baa3b5265.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/chat/page-45720e1ed71e3ef5.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/{layout-d0f0a9067427fb20.js → layout-86561d2fac35a91a.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/{page-ea462e20376b6dce.js → page-ecb8e1c192aa8834.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/search/layout-ea6b73fdaf9b24ca.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/search/{page-a5c277eff207959e.js → page-8e28deacb61f75aa.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/settings/{layout-a8f33dfe92f997fb.js → layout-254eaaf916449a60.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/settings/page-2fab613a557d3cc5.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/share/chat/layout-cf7445cf0326bda3.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/share/chat/page-30376aa7e9cfa342.js +1 -0
- khoj/interface/compiled/_next/static/chunks/{main-f84cd3c1873cd842.js → main-1ea5c2e0fdef4626.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/{webpack-8beec5b51cabb39a.js → webpack-27cf153c35b1338d.js} +1 -1
- khoj/interface/compiled/_next/static/css/{467a524c75e7d7c0.css → 0e9d53dcd7f11342.css} +1 -1
- khoj/interface/compiled/_next/static/css/{26c1c33d0423a7d8.css → 1f293605f2871853.css} +1 -1
- khoj/interface/compiled/_next/static/css/2d097a35da6bfe8d.css +1 -0
- khoj/interface/compiled/_next/static/css/80bd6301fc657983.css +1 -0
- khoj/interface/compiled/_next/static/css/ed437164d77aa600.css +25 -0
- khoj/interface/compiled/_next/static/media/5455839c73f146e7-s.p.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/5984b96ba4822821-s.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/684adc3dde1b03f1-s.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/82e3b9a1bdaf0c26-s.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/8d1ea331386a0db8-s.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/91475f6526542a4f-s.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/b98b13dbc1c3b59c-s.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/c824d7a20139e39d-s.woff2 +0 -0
- khoj/interface/compiled/agents/index.html +1 -1
- khoj/interface/compiled/agents/index.txt +2 -2
- khoj/interface/compiled/automations/index.html +1 -1
- khoj/interface/compiled/automations/index.txt +2 -2
- khoj/interface/compiled/chat/index.html +1 -1
- khoj/interface/compiled/chat/index.txt +2 -2
- khoj/interface/compiled/index.html +1 -1
- khoj/interface/compiled/index.txt +3 -3
- khoj/interface/compiled/search/index.html +1 -1
- khoj/interface/compiled/search/index.txt +2 -2
- khoj/interface/compiled/settings/index.html +1 -1
- khoj/interface/compiled/settings/index.txt +3 -3
- khoj/interface/compiled/share/chat/index.html +1 -1
- khoj/interface/compiled/share/chat/index.txt +3 -3
- khoj/processor/content/docx/docx_to_entries.py +27 -21
- khoj/processor/content/github/github_to_entries.py +2 -2
- khoj/processor/content/images/image_to_entries.py +2 -2
- khoj/processor/content/markdown/markdown_to_entries.py +2 -2
- khoj/processor/content/notion/notion_to_entries.py +2 -2
- khoj/processor/content/org_mode/org_to_entries.py +2 -2
- khoj/processor/content/org_mode/orgnode.py +1 -1
- khoj/processor/content/pdf/pdf_to_entries.py +37 -29
- khoj/processor/content/plaintext/plaintext_to_entries.py +2 -2
- khoj/processor/content/text_to_entries.py +3 -4
- khoj/processor/conversation/anthropic/anthropic_chat.py +9 -1
- khoj/processor/conversation/google/gemini_chat.py +15 -2
- khoj/processor/conversation/google/utils.py +3 -1
- khoj/processor/conversation/offline/chat_model.py +4 -0
- khoj/processor/conversation/openai/gpt.py +6 -1
- khoj/processor/conversation/prompts.py +72 -13
- khoj/processor/conversation/utils.py +80 -13
- khoj/processor/image/generate.py +2 -0
- khoj/processor/tools/online_search.py +68 -18
- khoj/processor/tools/run_code.py +54 -20
- khoj/routers/api.py +10 -4
- khoj/routers/api_agents.py +8 -10
- khoj/routers/api_chat.py +89 -24
- khoj/routers/api_content.py +80 -8
- khoj/routers/helpers.py +176 -60
- khoj/routers/notion.py +1 -1
- khoj/routers/research.py +73 -31
- khoj/routers/web_client.py +0 -10
- khoj/search_type/text_search.py +3 -7
- khoj/utils/cli.py +2 -2
- khoj/utils/fs_syncer.py +2 -1
- khoj/utils/helpers.py +6 -3
- khoj/utils/rawconfig.py +32 -0
- khoj/utils/state.py +2 -1
- {khoj-1.28.3.dist-info → khoj-1.28.4.dev92.dist-info}/METADATA +3 -3
- {khoj-1.28.3.dist-info → khoj-1.28.4.dev92.dist-info}/RECORD +99 -105
- {khoj-1.28.3.dist-info → khoj-1.28.4.dev92.dist-info}/WHEEL +1 -1
- khoj/interface/compiled/_next/static/chunks/1034-da58b679fcbb79c1.js +0 -1
- khoj/interface/compiled/_next/static/chunks/1467-b331e469fe411347.js +0 -1
- khoj/interface/compiled/_next/static/chunks/1603-c1568f45947e9f2c.js +0 -1
- khoj/interface/compiled/_next/static/chunks/1970-d44050bf658ae5cc.js +0 -1
- khoj/interface/compiled/_next/static/chunks/3110-ef2cacd1b8d79ad8.js +0 -1
- khoj/interface/compiled/_next/static/chunks/3423-f4b7df2f6f3362f7.js +0 -1
- khoj/interface/compiled/_next/static/chunks/394-6bcb8c429f168f21.js +0 -3
- khoj/interface/compiled/_next/static/chunks/7113-f2e114d7034a0835.js +0 -1
- khoj/interface/compiled/_next/static/chunks/8423-da57554315eebcbe.js +0 -1
- khoj/interface/compiled/_next/static/chunks/8840-b8d7b9f0923c6651.js +0 -1
- khoj/interface/compiled/_next/static/chunks/9417-0d0fc7eb49a86abb.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/agents/layout-75636ab3a413fa8e.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/agents/page-adbf3cd470da248f.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/chat/layout-96fcf62857bf8f30.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/chat/page-222d348681b848a5.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/factchecker/layout-7b30c541c05fb904.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/factchecker/page-bded0868a08ac4ba.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/search/layout-3720f1362310bebb.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/settings/page-210bd54db4841333.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/share/chat/layout-2df56074e42adaa0.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/share/chat/page-a21b7e8890ed1209.js +0 -1
- khoj/interface/compiled/_next/static/css/4cae6c0e5c72fb2d.css +0 -1
- khoj/interface/compiled/_next/static/css/553f9cdcc7a2bcd6.css +0 -1
- khoj/interface/compiled/_next/static/css/a795ee88875f4853.css +0 -25
- khoj/interface/compiled/_next/static/css/afd3d45cc65d55d8.css +0 -1
- khoj/interface/compiled/_next/static/media/0e790e04fd40ad16-s.p.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/4221e1667cd19c7d-s.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/6c276159aa0eb14b-s.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/6cc0b9500e4f9168-s.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/9d9319a7a2ac39c6-s.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/a75c8ea86756d52d-s.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/abce7c400ca31a51-s.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/f759c939737fb668-s.woff2 +0 -0
- khoj/interface/compiled/factchecker/index.html +0 -1
- khoj/interface/compiled/factchecker/index.txt +0 -7
- /khoj/interface/compiled/_next/static/{EfnEiWDle86AUcxEdEFgO → t_2jovvUVve0Gvc3FqpT9}/_buildManifest.js +0 -0
- /khoj/interface/compiled/_next/static/{EfnEiWDle86AUcxEdEFgO → t_2jovvUVve0Gvc3FqpT9}/_ssgManifest.js +0 -0
- {khoj-1.28.3.dist-info → khoj-1.28.4.dev92.dist-info}/entry_points.txt +0 -0
- {khoj-1.28.3.dist-info → khoj-1.28.4.dev92.dist-info}/licenses/LICENSE +0 -0
khoj/configure.py
CHANGED
@@ -168,12 +168,6 @@ class UserAuthenticationBackend(AuthenticationBackend):
|
|
168
168
|
if create_if_not_exists:
|
169
169
|
user, is_new = await aget_or_create_user_by_phone_number(phone_number)
|
170
170
|
if user and is_new:
|
171
|
-
update_telemetry_state(
|
172
|
-
request=request,
|
173
|
-
telemetry_type="api",
|
174
|
-
api="create_user",
|
175
|
-
metadata={"server_id": str(user.uuid)},
|
176
|
-
)
|
177
171
|
logger.log(logging.INFO, f"🥳 New User Created: {user.uuid}")
|
178
172
|
else:
|
179
173
|
user = await aget_user_by_phone_number(phone_number)
|
@@ -255,11 +249,15 @@ def configure_server(
|
|
255
249
|
state.search_models = configure_search(state.search_models, state.config.search_type)
|
256
250
|
setup_default_agent(user)
|
257
251
|
|
258
|
-
message =
|
252
|
+
message = (
|
253
|
+
"📡 Telemetry disabled"
|
254
|
+
if telemetry_disabled(state.config.app, state.telemetry_disabled)
|
255
|
+
else "📡 Telemetry enabled"
|
256
|
+
)
|
259
257
|
logger.info(message)
|
260
258
|
|
261
259
|
if not init:
|
262
|
-
initialize_content(regenerate, search_type
|
260
|
+
initialize_content(user, regenerate, search_type)
|
263
261
|
|
264
262
|
except Exception as e:
|
265
263
|
logger.error(f"Failed to load some search models: {e}", exc_info=True)
|
@@ -269,17 +267,17 @@ def setup_default_agent(user: KhojUser):
|
|
269
267
|
AgentAdapters.create_default_agent(user)
|
270
268
|
|
271
269
|
|
272
|
-
def initialize_content(regenerate: bool, search_type: Optional[SearchType] = None
|
270
|
+
def initialize_content(user: KhojUser, regenerate: bool, search_type: Optional[SearchType] = None):
|
273
271
|
# Initialize Content from Config
|
274
272
|
if state.search_models:
|
275
273
|
try:
|
276
274
|
logger.info("📬 Updating content index...")
|
277
275
|
all_files = collect_files(user=user)
|
278
276
|
status = configure_content(
|
277
|
+
user,
|
279
278
|
all_files,
|
280
279
|
regenerate,
|
281
280
|
search_type,
|
282
|
-
user=user,
|
283
281
|
)
|
284
282
|
if not status:
|
285
283
|
raise RuntimeError("Failed to update content index")
|
@@ -344,9 +342,7 @@ def configure_middleware(app):
|
|
344
342
|
def update_content_index():
|
345
343
|
for user in get_all_users():
|
346
344
|
all_files = collect_files(user=user)
|
347
|
-
success = configure_content(
|
348
|
-
all_files = collect_files(user=None)
|
349
|
-
success = configure_content(all_files, user=None)
|
345
|
+
success = configure_content(user, all_files)
|
350
346
|
if not success:
|
351
347
|
raise RuntimeError("Failed to update content index")
|
352
348
|
logger.info("📪 Content index updated via Scheduler")
|
@@ -369,7 +365,7 @@ def configure_search_types():
|
|
369
365
|
|
370
366
|
@schedule.repeat(schedule.every(2).minutes)
|
371
367
|
def upload_telemetry():
|
372
|
-
if telemetry_disabled(state.config.app) or not state.telemetry:
|
368
|
+
if telemetry_disabled(state.config.app, state.telemetry_disabled) or not state.telemetry:
|
373
369
|
return
|
374
370
|
|
375
371
|
try:
|
@@ -8,13 +8,22 @@ import secrets
|
|
8
8
|
import sys
|
9
9
|
from datetime import date, datetime, timedelta, timezone
|
10
10
|
from enum import Enum
|
11
|
-
from
|
11
|
+
from functools import wraps
|
12
|
+
from typing import (
|
13
|
+
Any,
|
14
|
+
Callable,
|
15
|
+
Coroutine,
|
16
|
+
Iterable,
|
17
|
+
List,
|
18
|
+
Optional,
|
19
|
+
ParamSpec,
|
20
|
+
TypeVar,
|
21
|
+
)
|
12
22
|
|
13
23
|
import cron_descriptor
|
14
24
|
from apscheduler.job import Job
|
15
25
|
from asgiref.sync import sync_to_async
|
16
26
|
from django.contrib.sessions.backends.db import SessionStore
|
17
|
-
from django.db import models
|
18
27
|
from django.db.models import Prefetch, Q
|
19
28
|
from django.db.models.manager import BaseManager
|
20
29
|
from django.db.utils import IntegrityError
|
@@ -28,7 +37,6 @@ from khoj.database.models import (
|
|
28
37
|
ChatModelOptions,
|
29
38
|
ClientApplication,
|
30
39
|
Conversation,
|
31
|
-
DataStore,
|
32
40
|
Entry,
|
33
41
|
FileObject,
|
34
42
|
GithubConfig,
|
@@ -48,7 +56,6 @@ from khoj.database.models import (
|
|
48
56
|
TextToImageModelConfig,
|
49
57
|
UserConversationConfig,
|
50
58
|
UserRequests,
|
51
|
-
UserSearchModelConfig,
|
52
59
|
UserTextToImageModelConfig,
|
53
60
|
UserVoiceModelConfig,
|
54
61
|
VoiceModelOption,
|
@@ -81,6 +88,45 @@ class SubscriptionState(Enum):
|
|
81
88
|
INVALID = "invalid"
|
82
89
|
|
83
90
|
|
91
|
+
P = ParamSpec("P")
|
92
|
+
T = TypeVar("T")
|
93
|
+
|
94
|
+
|
95
|
+
def require_valid_user(func: Callable[P, T]) -> Callable[P, T]:
|
96
|
+
@wraps(func)
|
97
|
+
def sync_wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
98
|
+
# Extract user from args/kwargs
|
99
|
+
user = next((arg for arg in args if isinstance(arg, KhojUser)), None)
|
100
|
+
if not user:
|
101
|
+
user = next((val for val in kwargs.values() if isinstance(val, KhojUser)), None)
|
102
|
+
|
103
|
+
# Throw error if user is not found
|
104
|
+
if not user:
|
105
|
+
raise ValueError("Khoj user argument required but not provided.")
|
106
|
+
|
107
|
+
return func(*args, **kwargs)
|
108
|
+
|
109
|
+
return sync_wrapper
|
110
|
+
|
111
|
+
|
112
|
+
def arequire_valid_user(func: Callable[P, Coroutine[Any, Any, T]]) -> Callable[P, Coroutine[Any, Any, T]]:
|
113
|
+
@wraps(func)
|
114
|
+
async def async_wrapper(*args: P.args, **kwargs: P.kwargs) -> T:
|
115
|
+
# Extract user from args/kwargs
|
116
|
+
user = next((arg for arg in args if isinstance(arg, KhojUser)), None)
|
117
|
+
if not user:
|
118
|
+
user = next((v for v in kwargs.values() if isinstance(v, KhojUser)), None)
|
119
|
+
|
120
|
+
# Throw error if user is not found
|
121
|
+
if not user:
|
122
|
+
raise ValueError("Khoj user argument required but not provided.")
|
123
|
+
|
124
|
+
return await func(*args, **kwargs)
|
125
|
+
|
126
|
+
return async_wrapper
|
127
|
+
|
128
|
+
|
129
|
+
@arequire_valid_user
|
84
130
|
async def set_notion_config(token: str, user: KhojUser):
|
85
131
|
notion_config = await NotionConfig.objects.filter(user=user).afirst()
|
86
132
|
if not notion_config:
|
@@ -91,6 +137,7 @@ async def set_notion_config(token: str, user: KhojUser):
|
|
91
137
|
return notion_config
|
92
138
|
|
93
139
|
|
140
|
+
@require_valid_user
|
94
141
|
def create_khoj_token(user: KhojUser, name=None):
|
95
142
|
"Create Khoj API key for user"
|
96
143
|
token = f"kk-{secrets.token_urlsafe(32)}"
|
@@ -98,6 +145,7 @@ def create_khoj_token(user: KhojUser, name=None):
|
|
98
145
|
return KhojApiUser.objects.create(token=token, user=user, name=name)
|
99
146
|
|
100
147
|
|
148
|
+
@arequire_valid_user
|
101
149
|
async def acreate_khoj_token(user: KhojUser, name=None):
|
102
150
|
"Create Khoj API key for user"
|
103
151
|
token = f"kk-{secrets.token_urlsafe(32)}"
|
@@ -105,11 +153,13 @@ async def acreate_khoj_token(user: KhojUser, name=None):
|
|
105
153
|
return await KhojApiUser.objects.acreate(token=token, user=user, name=name)
|
106
154
|
|
107
155
|
|
156
|
+
@require_valid_user
|
108
157
|
def get_khoj_tokens(user: KhojUser):
|
109
158
|
"Get all Khoj API keys for user"
|
110
159
|
return list(KhojApiUser.objects.filter(user=user))
|
111
160
|
|
112
161
|
|
162
|
+
@arequire_valid_user
|
113
163
|
async def delete_khoj_token(user: KhojUser, token: str):
|
114
164
|
"Delete Khoj API Key for user"
|
115
165
|
await KhojApiUser.objects.filter(token=token, user=user).adelete()
|
@@ -133,6 +183,7 @@ async def aget_or_create_user_by_phone_number(phone_number: str) -> tuple[KhojUs
|
|
133
183
|
return user, is_new
|
134
184
|
|
135
185
|
|
186
|
+
@arequire_valid_user
|
136
187
|
async def aset_user_phone_number(user: KhojUser, phone_number: str) -> KhojUser:
|
137
188
|
if is_none_or_empty(phone_number):
|
138
189
|
return None
|
@@ -156,6 +207,7 @@ async def aset_user_phone_number(user: KhojUser, phone_number: str) -> KhojUser:
|
|
156
207
|
return user
|
157
208
|
|
158
209
|
|
210
|
+
@arequire_valid_user
|
159
211
|
async def aremove_phone_number(user: KhojUser) -> KhojUser:
|
160
212
|
user.phone_number = None
|
161
213
|
user.verified_phone_number = False
|
@@ -193,6 +245,7 @@ async def aget_or_create_user_by_email(email: str) -> tuple[KhojUser, bool]:
|
|
193
245
|
return user, is_new
|
194
246
|
|
195
247
|
|
248
|
+
@arequire_valid_user
|
196
249
|
async def astart_trial_subscription(user: KhojUser) -> Subscription:
|
197
250
|
subscription = await Subscription.objects.filter(user=user).afirst()
|
198
251
|
if not subscription:
|
@@ -247,6 +300,7 @@ async def create_user_by_google_token(token: dict) -> KhojUser:
|
|
247
300
|
return user
|
248
301
|
|
249
302
|
|
303
|
+
@require_valid_user
|
250
304
|
def set_user_name(user: KhojUser, first_name: str, last_name: str) -> KhojUser:
|
251
305
|
user.first_name = first_name
|
252
306
|
user.last_name = last_name
|
@@ -254,6 +308,7 @@ def set_user_name(user: KhojUser, first_name: str, last_name: str) -> KhojUser:
|
|
254
308
|
return user
|
255
309
|
|
256
310
|
|
311
|
+
@require_valid_user
|
257
312
|
def get_user_name(user: KhojUser):
|
258
313
|
full_name = user.get_full_name()
|
259
314
|
if not is_none_or_empty(full_name):
|
@@ -265,6 +320,7 @@ def get_user_name(user: KhojUser):
|
|
265
320
|
return None
|
266
321
|
|
267
322
|
|
323
|
+
@require_valid_user
|
268
324
|
def get_user_photo(user: KhojUser):
|
269
325
|
google_profile: GoogleUser = GoogleUser.objects.filter(user=user).first()
|
270
326
|
if google_profile:
|
@@ -328,6 +384,7 @@ def get_user_subscription_state(email: str) -> str:
|
|
328
384
|
return subscription_to_state(user_subscription)
|
329
385
|
|
330
386
|
|
387
|
+
@arequire_valid_user
|
331
388
|
async def aget_user_subscription_state(user: KhojUser) -> str:
|
332
389
|
"""Get subscription state of user
|
333
390
|
Valid state transitions: trial -> subscribed <-> unsubscribed OR expired
|
@@ -336,6 +393,7 @@ async def aget_user_subscription_state(user: KhojUser) -> str:
|
|
336
393
|
return await sync_to_async(subscription_to_state)(user_subscription)
|
337
394
|
|
338
395
|
|
396
|
+
@arequire_valid_user
|
339
397
|
async def ais_user_subscribed(user: KhojUser) -> bool:
|
340
398
|
"""
|
341
399
|
Get whether the user is subscribed
|
@@ -352,6 +410,7 @@ async def ais_user_subscribed(user: KhojUser) -> bool:
|
|
352
410
|
return subscribed
|
353
411
|
|
354
412
|
|
413
|
+
@require_valid_user
|
355
414
|
def is_user_subscribed(user: KhojUser) -> bool:
|
356
415
|
"""
|
357
416
|
Get whether the user is subscribed
|
@@ -417,11 +476,13 @@ def get_all_users() -> BaseManager[KhojUser]:
|
|
417
476
|
return KhojUser.objects.all()
|
418
477
|
|
419
478
|
|
479
|
+
@require_valid_user
|
420
480
|
def get_user_github_config(user: KhojUser):
|
421
481
|
config = GithubConfig.objects.filter(user=user).prefetch_related("githubrepoconfig").first()
|
422
482
|
return config
|
423
483
|
|
424
484
|
|
485
|
+
@require_valid_user
|
425
486
|
def get_user_notion_config(user: KhojUser):
|
426
487
|
config = NotionConfig.objects.filter(user=user).first()
|
427
488
|
return config
|
@@ -431,6 +492,7 @@ def delete_user_requests(window: timedelta = timedelta(days=1)):
|
|
431
492
|
return UserRequests.objects.filter(created_at__lte=datetime.now(tz=timezone.utc) - window).delete()
|
432
493
|
|
433
494
|
|
495
|
+
@arequire_valid_user
|
434
496
|
async def aget_user_name(user: KhojUser):
|
435
497
|
full_name = user.get_full_name()
|
436
498
|
if not is_none_or_empty(full_name):
|
@@ -442,18 +504,7 @@ async def aget_user_name(user: KhojUser):
|
|
442
504
|
return None
|
443
505
|
|
444
506
|
|
445
|
-
|
446
|
-
deduped_files = list(set(updated_config.input_files)) if updated_config.input_files else None
|
447
|
-
deduped_filters = list(set(updated_config.input_filter)) if updated_config.input_filter else None
|
448
|
-
await object.objects.filter(user=user).adelete()
|
449
|
-
await object.objects.acreate(
|
450
|
-
input_files=deduped_files,
|
451
|
-
input_filter=deduped_filters,
|
452
|
-
index_heading_entries=updated_config.index_heading_entries,
|
453
|
-
user=user,
|
454
|
-
)
|
455
|
-
|
456
|
-
|
507
|
+
@arequire_valid_user
|
457
508
|
async def set_user_github_config(user: KhojUser, pat_token: str, repos: list):
|
458
509
|
config = await GithubConfig.objects.filter(user=user).afirst()
|
459
510
|
|
@@ -481,15 +532,6 @@ def get_default_search_model() -> SearchModelConfig:
|
|
481
532
|
return SearchModelConfig.objects.first()
|
482
533
|
|
483
534
|
|
484
|
-
def get_user_default_search_model(user: KhojUser = None) -> SearchModelConfig:
|
485
|
-
if user:
|
486
|
-
user_search_model = UserSearchModelConfig.objects.filter(user=user).first()
|
487
|
-
if user_search_model:
|
488
|
-
return user_search_model.setting
|
489
|
-
|
490
|
-
return get_default_search_model()
|
491
|
-
|
492
|
-
|
493
535
|
def get_or_create_search_models():
|
494
536
|
search_models = SearchModelConfig.objects.all()
|
495
537
|
if search_models.count() == 0:
|
@@ -597,8 +639,11 @@ class AgentAdapters:
|
|
597
639
|
)
|
598
640
|
|
599
641
|
@staticmethod
|
642
|
+
@arequire_valid_user
|
600
643
|
async def adelete_agent_by_slug(agent_slug: str, user: KhojUser):
|
601
644
|
agent = await AgentAdapters.aget_agent_by_slug(agent_slug, user)
|
645
|
+
if agent.creator != user:
|
646
|
+
return False
|
602
647
|
|
603
648
|
async for entry in Entry.objects.filter(agent=agent).aiterator():
|
604
649
|
await entry.adelete()
|
@@ -722,6 +767,7 @@ class AgentAdapters:
|
|
722
767
|
return await Agent.objects.filter(name=AgentAdapters.DEFAULT_AGENT_NAME).afirst()
|
723
768
|
|
724
769
|
@staticmethod
|
770
|
+
@arequire_valid_user
|
725
771
|
async def aupdate_agent(
|
726
772
|
user: KhojUser,
|
727
773
|
name: str,
|
@@ -797,19 +843,6 @@ class PublicConversationAdapters:
|
|
797
843
|
return f"/share/chat/{public_conversation.slug}/"
|
798
844
|
|
799
845
|
|
800
|
-
class DataStoreAdapters:
|
801
|
-
@staticmethod
|
802
|
-
async def astore_data(data: dict, key: str, user: KhojUser, private: bool = True):
|
803
|
-
if await DataStore.objects.filter(key=key).aexists():
|
804
|
-
return key
|
805
|
-
await DataStore.objects.acreate(value=data, key=key, owner=user, private=private)
|
806
|
-
return key
|
807
|
-
|
808
|
-
@staticmethod
|
809
|
-
async def aretrieve_public_data(key: str):
|
810
|
-
return await DataStore.objects.filter(key=key, private=False).afirst()
|
811
|
-
|
812
|
-
|
813
846
|
class ConversationAdapters:
|
814
847
|
@staticmethod
|
815
848
|
def make_public_conversation_copy(conversation: Conversation):
|
@@ -822,6 +855,7 @@ class ConversationAdapters:
|
|
822
855
|
)
|
823
856
|
|
824
857
|
@staticmethod
|
858
|
+
@require_valid_user
|
825
859
|
def get_conversation_by_user(
|
826
860
|
user: KhojUser, client_application: ClientApplication = None, conversation_id: str = None
|
827
861
|
) -> Optional[Conversation]:
|
@@ -840,6 +874,7 @@ class ConversationAdapters:
|
|
840
874
|
return conversation
|
841
875
|
|
842
876
|
@staticmethod
|
877
|
+
@require_valid_user
|
843
878
|
def get_conversation_sessions(user: KhojUser, client_application: ClientApplication = None):
|
844
879
|
return (
|
845
880
|
Conversation.objects.filter(user=user, client=client_application)
|
@@ -848,6 +883,7 @@ class ConversationAdapters:
|
|
848
883
|
)
|
849
884
|
|
850
885
|
@staticmethod
|
886
|
+
@arequire_valid_user
|
851
887
|
async def aset_conversation_title(
|
852
888
|
user: KhojUser, client_application: ClientApplication, conversation_id: str, title: str
|
853
889
|
):
|
@@ -865,6 +901,7 @@ class ConversationAdapters:
|
|
865
901
|
return Conversation.objects.filter(id=conversation_id).first()
|
866
902
|
|
867
903
|
@staticmethod
|
904
|
+
@arequire_valid_user
|
868
905
|
async def acreate_conversation_session(
|
869
906
|
user: KhojUser, client_application: ClientApplication = None, agent_slug: str = None, title: str = None
|
870
907
|
):
|
@@ -881,6 +918,7 @@ class ConversationAdapters:
|
|
881
918
|
)
|
882
919
|
|
883
920
|
@staticmethod
|
921
|
+
@require_valid_user
|
884
922
|
def create_conversation_session(
|
885
923
|
user: KhojUser, client_application: ClientApplication = None, agent_slug: str = None, title: str = None
|
886
924
|
):
|
@@ -893,6 +931,7 @@ class ConversationAdapters:
|
|
893
931
|
return Conversation.objects.create(user=user, client=client_application, agent=agent, title=title)
|
894
932
|
|
895
933
|
@staticmethod
|
934
|
+
@arequire_valid_user
|
896
935
|
async def aget_conversation_by_user(
|
897
936
|
user: KhojUser,
|
898
937
|
client_application: ClientApplication = None,
|
@@ -917,6 +956,7 @@ class ConversationAdapters:
|
|
917
956
|
)
|
918
957
|
|
919
958
|
@staticmethod
|
959
|
+
@arequire_valid_user
|
920
960
|
async def adelete_conversation_by_user(
|
921
961
|
user: KhojUser, client_application: ClientApplication = None, conversation_id: str = None
|
922
962
|
):
|
@@ -925,6 +965,7 @@ class ConversationAdapters:
|
|
925
965
|
return await Conversation.objects.filter(user=user, client=client_application).adelete()
|
926
966
|
|
927
967
|
@staticmethod
|
968
|
+
@require_valid_user
|
928
969
|
def has_any_conversation_config(user: KhojUser):
|
929
970
|
return ChatModelOptions.objects.filter(user=user).exists()
|
930
971
|
|
@@ -961,6 +1002,7 @@ class ConversationAdapters:
|
|
961
1002
|
return OpenAIProcessorConversationConfig.objects.filter().exists()
|
962
1003
|
|
963
1004
|
@staticmethod
|
1005
|
+
@arequire_valid_user
|
964
1006
|
async def aset_user_conversation_processor(user: KhojUser, conversation_processor_config_id: int):
|
965
1007
|
config = await ChatModelOptions.objects.filter(id=conversation_processor_config_id).afirst()
|
966
1008
|
if not config:
|
@@ -969,6 +1011,7 @@ class ConversationAdapters:
|
|
969
1011
|
return new_config
|
970
1012
|
|
971
1013
|
@staticmethod
|
1014
|
+
@arequire_valid_user
|
972
1015
|
async def aset_user_voice_model(user: KhojUser, model_id: str):
|
973
1016
|
config = await VoiceModelOption.objects.filter(model_id=model_id).afirst()
|
974
1017
|
if not config:
|
@@ -1153,6 +1196,7 @@ class ConversationAdapters:
|
|
1153
1196
|
return enabled_scrapers
|
1154
1197
|
|
1155
1198
|
@staticmethod
|
1199
|
+
@require_valid_user
|
1156
1200
|
def create_conversation_from_public_conversation(
|
1157
1201
|
user: KhojUser, public_conversation: PublicConversation, client_app: ClientApplication
|
1158
1202
|
):
|
@@ -1169,6 +1213,7 @@ class ConversationAdapters:
|
|
1169
1213
|
)
|
1170
1214
|
|
1171
1215
|
@staticmethod
|
1216
|
+
@require_valid_user
|
1172
1217
|
def save_conversation(
|
1173
1218
|
user: KhojUser,
|
1174
1219
|
conversation_log: dict,
|
@@ -1218,6 +1263,7 @@ class ConversationAdapters:
|
|
1218
1263
|
return await SpeechToTextModelOptions.objects.filter().afirst()
|
1219
1264
|
|
1220
1265
|
@staticmethod
|
1266
|
+
@arequire_valid_user
|
1221
1267
|
async def aget_conversation_starters(user: KhojUser, max_results=3):
|
1222
1268
|
all_questions = []
|
1223
1269
|
if await ReflectiveQuestion.objects.filter(user=user).aexists():
|
@@ -1347,6 +1393,7 @@ class ConversationAdapters:
|
|
1347
1393
|
return conversation.file_filters
|
1348
1394
|
|
1349
1395
|
@staticmethod
|
1396
|
+
@require_valid_user
|
1350
1397
|
def delete_message_by_turn_id(user: KhojUser, conversation_id: str, turn_id: str):
|
1351
1398
|
conversation = ConversationAdapters.get_conversation_by_user(user, conversation_id=conversation_id)
|
1352
1399
|
if not conversation or not conversation.conversation_log or not conversation.conversation_log.get("chat"):
|
@@ -1365,48 +1412,63 @@ class FileObjectAdapters:
|
|
1365
1412
|
file_object.save()
|
1366
1413
|
|
1367
1414
|
@staticmethod
|
1415
|
+
@require_valid_user
|
1368
1416
|
def create_file_object(user: KhojUser, file_name: str, raw_text: str):
|
1369
1417
|
return FileObject.objects.create(user=user, file_name=file_name, raw_text=raw_text)
|
1370
1418
|
|
1371
1419
|
@staticmethod
|
1420
|
+
@require_valid_user
|
1372
1421
|
def get_file_object_by_name(user: KhojUser, file_name: str):
|
1373
1422
|
return FileObject.objects.filter(user=user, file_name=file_name).first()
|
1374
1423
|
|
1375
1424
|
@staticmethod
|
1425
|
+
@require_valid_user
|
1376
1426
|
def get_all_file_objects(user: KhojUser):
|
1377
1427
|
return FileObject.objects.filter(user=user).all()
|
1378
1428
|
|
1379
1429
|
@staticmethod
|
1430
|
+
@require_valid_user
|
1380
1431
|
def delete_file_object_by_name(user: KhojUser, file_name: str):
|
1381
1432
|
return FileObject.objects.filter(user=user, file_name=file_name).delete()
|
1382
1433
|
|
1383
1434
|
@staticmethod
|
1435
|
+
@require_valid_user
|
1384
1436
|
def delete_all_file_objects(user: KhojUser):
|
1385
1437
|
return FileObject.objects.filter(user=user).delete()
|
1386
1438
|
|
1387
1439
|
@staticmethod
|
1388
|
-
async def
|
1440
|
+
async def aupdate_raw_text(file_object: FileObject, new_raw_text: str):
|
1389
1441
|
file_object.raw_text = new_raw_text
|
1390
1442
|
await file_object.asave()
|
1391
1443
|
|
1392
1444
|
@staticmethod
|
1393
|
-
|
1445
|
+
@arequire_valid_user
|
1446
|
+
async def acreate_file_object(user: KhojUser, file_name: str, raw_text: str):
|
1394
1447
|
return await FileObject.objects.acreate(user=user, file_name=file_name, raw_text=raw_text)
|
1395
1448
|
|
1396
1449
|
@staticmethod
|
1397
|
-
|
1450
|
+
@arequire_valid_user
|
1451
|
+
async def aget_file_objects_by_name(user: KhojUser, file_name: str, agent: Agent = None):
|
1398
1452
|
return await sync_to_async(list)(FileObject.objects.filter(user=user, file_name=file_name, agent=agent))
|
1399
1453
|
|
1400
1454
|
@staticmethod
|
1401
|
-
|
1455
|
+
@arequire_valid_user
|
1456
|
+
async def aget_file_objects_by_names(user: KhojUser, file_names: List[str]):
|
1457
|
+
return await sync_to_async(list)(FileObject.objects.filter(user=user, file_name__in=file_names))
|
1458
|
+
|
1459
|
+
@staticmethod
|
1460
|
+
@arequire_valid_user
|
1461
|
+
async def aget_all_file_objects(user: KhojUser):
|
1402
1462
|
return await sync_to_async(list)(FileObject.objects.filter(user=user))
|
1403
1463
|
|
1404
1464
|
@staticmethod
|
1405
|
-
|
1465
|
+
@arequire_valid_user
|
1466
|
+
async def adelete_file_object_by_name(user: KhojUser, file_name: str):
|
1406
1467
|
return await FileObject.objects.filter(user=user, file_name=file_name).adelete()
|
1407
1468
|
|
1408
1469
|
@staticmethod
|
1409
|
-
|
1470
|
+
@arequire_valid_user
|
1471
|
+
async def adelete_all_file_objects(user: KhojUser):
|
1410
1472
|
return await FileObject.objects.filter(user=user).adelete()
|
1411
1473
|
|
1412
1474
|
|
@@ -1416,15 +1478,18 @@ class EntryAdapters:
|
|
1416
1478
|
date_filter = DateFilter()
|
1417
1479
|
|
1418
1480
|
@staticmethod
|
1481
|
+
@require_valid_user
|
1419
1482
|
def does_entry_exist(user: KhojUser, hashed_value: str) -> bool:
|
1420
1483
|
return Entry.objects.filter(user=user, hashed_value=hashed_value).exists()
|
1421
1484
|
|
1422
1485
|
@staticmethod
|
1486
|
+
@require_valid_user
|
1423
1487
|
def delete_entry_by_file(user: KhojUser, file_path: str):
|
1424
1488
|
deleted_count, _ = Entry.objects.filter(user=user, file_path=file_path).delete()
|
1425
1489
|
return deleted_count
|
1426
1490
|
|
1427
1491
|
@staticmethod
|
1492
|
+
@require_valid_user
|
1428
1493
|
def get_filtered_entries(user: KhojUser, file_type: str = None, file_source: str = None):
|
1429
1494
|
queryset = Entry.objects.filter(user=user)
|
1430
1495
|
|
@@ -1437,6 +1502,7 @@ class EntryAdapters:
|
|
1437
1502
|
return queryset
|
1438
1503
|
|
1439
1504
|
@staticmethod
|
1505
|
+
@require_valid_user
|
1440
1506
|
def delete_all_entries(user: KhojUser, file_type: str = None, file_source: str = None, batch_size=1000):
|
1441
1507
|
deleted_count = 0
|
1442
1508
|
queryset = EntryAdapters.get_filtered_entries(user, file_type, file_source)
|
@@ -1448,6 +1514,7 @@ class EntryAdapters:
|
|
1448
1514
|
return deleted_count
|
1449
1515
|
|
1450
1516
|
@staticmethod
|
1517
|
+
@arequire_valid_user
|
1451
1518
|
async def adelete_all_entries(user: KhojUser, file_type: str = None, file_source: str = None, batch_size=1000):
|
1452
1519
|
deleted_count = 0
|
1453
1520
|
queryset = EntryAdapters.get_filtered_entries(user, file_type, file_source)
|
@@ -1459,10 +1526,12 @@ class EntryAdapters:
|
|
1459
1526
|
return deleted_count
|
1460
1527
|
|
1461
1528
|
@staticmethod
|
1529
|
+
@require_valid_user
|
1462
1530
|
def get_existing_entry_hashes_by_file(user: KhojUser, file_path: str):
|
1463
1531
|
return Entry.objects.filter(user=user, file_path=file_path).values_list("hashed_value", flat=True)
|
1464
1532
|
|
1465
1533
|
@staticmethod
|
1534
|
+
@require_valid_user
|
1466
1535
|
def delete_entry_by_hash(user: KhojUser, hashed_values: List[str]):
|
1467
1536
|
Entry.objects.filter(user=user, hashed_value__in=hashed_values).delete()
|
1468
1537
|
|
@@ -1474,6 +1543,7 @@ class EntryAdapters:
|
|
1474
1543
|
)
|
1475
1544
|
|
1476
1545
|
@staticmethod
|
1546
|
+
@require_valid_user
|
1477
1547
|
def user_has_entries(user: KhojUser):
|
1478
1548
|
return Entry.objects.filter(user=user).exists()
|
1479
1549
|
|
@@ -1482,18 +1552,23 @@ class EntryAdapters:
|
|
1482
1552
|
return Entry.objects.filter(agent=agent).exists()
|
1483
1553
|
|
1484
1554
|
@staticmethod
|
1555
|
+
@arequire_valid_user
|
1485
1556
|
async def auser_has_entries(user: KhojUser):
|
1486
1557
|
return await Entry.objects.filter(user=user).aexists()
|
1487
1558
|
|
1488
1559
|
@staticmethod
|
1489
1560
|
async def aagent_has_entries(agent: Agent):
|
1561
|
+
if agent is None:
|
1562
|
+
return False
|
1490
1563
|
return await Entry.objects.filter(agent=agent).aexists()
|
1491
1564
|
|
1492
1565
|
@staticmethod
|
1566
|
+
@arequire_valid_user
|
1493
1567
|
async def adelete_entry_by_file(user: KhojUser, file_path: str):
|
1494
1568
|
return await Entry.objects.filter(user=user, file_path=file_path).adelete()
|
1495
1569
|
|
1496
1570
|
@staticmethod
|
1571
|
+
@arequire_valid_user
|
1497
1572
|
async def adelete_entries_by_filenames(user: KhojUser, filenames: List[str], batch_size=1000):
|
1498
1573
|
deleted_count = 0
|
1499
1574
|
for i in range(0, len(filenames), batch_size):
|
@@ -1505,11 +1580,14 @@ class EntryAdapters:
|
|
1505
1580
|
|
1506
1581
|
@staticmethod
|
1507
1582
|
async def aget_agent_entry_filepaths(agent: Agent):
|
1583
|
+
if agent is None:
|
1584
|
+
return []
|
1508
1585
|
return await sync_to_async(set)(
|
1509
1586
|
Entry.objects.filter(agent=agent).distinct("file_path").values_list("file_path", flat=True)
|
1510
1587
|
)
|
1511
1588
|
|
1512
1589
|
@staticmethod
|
1590
|
+
@require_valid_user
|
1513
1591
|
def get_all_filenames_by_source(user: KhojUser, file_source: str):
|
1514
1592
|
return (
|
1515
1593
|
Entry.objects.filter(user=user, file_source=file_source)
|
@@ -1518,6 +1596,7 @@ class EntryAdapters:
|
|
1518
1596
|
)
|
1519
1597
|
|
1520
1598
|
@staticmethod
|
1599
|
+
@require_valid_user
|
1521
1600
|
def get_size_of_indexed_data_in_mb(user: KhojUser):
|
1522
1601
|
entries = Entry.objects.filter(user=user).iterator()
|
1523
1602
|
total_size = sum(sys.getsizeof(entry.compiled) for entry in entries)
|
@@ -1538,6 +1617,9 @@ class EntryAdapters:
|
|
1538
1617
|
if agent != None:
|
1539
1618
|
owner_filter |= Q(agent=agent)
|
1540
1619
|
|
1620
|
+
if owner_filter == Q():
|
1621
|
+
return Entry.objects.none()
|
1622
|
+
|
1541
1623
|
if len(word_filters) == 0 and len(file_filters) == 0 and len(date_filters) == 0:
|
1542
1624
|
return Entry.objects.filter(owner_filter)
|
1543
1625
|
|
@@ -1612,10 +1694,12 @@ class EntryAdapters:
|
|
1612
1694
|
return relevant_entries[:max_results]
|
1613
1695
|
|
1614
1696
|
@staticmethod
|
1697
|
+
@require_valid_user
|
1615
1698
|
def get_unique_file_types(user: KhojUser):
|
1616
1699
|
return Entry.objects.filter(user=user).values_list("file_type", flat=True).distinct()
|
1617
1700
|
|
1618
1701
|
@staticmethod
|
1702
|
+
@require_valid_user
|
1619
1703
|
def get_unique_file_sources(user: KhojUser):
|
1620
1704
|
return Entry.objects.filter(user=user).values_list("file_source", flat=True).distinct().all()
|
1621
1705
|
|
khoj/database/admin.py
CHANGED
@@ -28,7 +28,6 @@ from khoj.database.models import (
|
|
28
28
|
TextToImageModelConfig,
|
29
29
|
UserConversationConfig,
|
30
30
|
UserRequests,
|
31
|
-
UserSearchModelConfig,
|
32
31
|
UserVoiceModelConfig,
|
33
32
|
VoiceModelOption,
|
34
33
|
WebScraper,
|
@@ -79,7 +78,12 @@ class KhojUserAdmin(UserAdmin):
|
|
79
78
|
search_fields = ("email", "username", "phone_number", "uuid")
|
80
79
|
filter_horizontal = ("groups", "user_permissions")
|
81
80
|
|
82
|
-
fieldsets = (
|
81
|
+
fieldsets = (
|
82
|
+
(
|
83
|
+
"Personal info",
|
84
|
+
{"fields": ("phone_number", "email_verification_code", "verified_phone_number", "verified_email")},
|
85
|
+
),
|
86
|
+
) + UserAdmin.fieldsets
|
83
87
|
|
84
88
|
actions = ["get_email_login_url"]
|
85
89
|
|
@@ -99,7 +103,6 @@ admin.site.register(KhojUser, KhojUserAdmin)
|
|
99
103
|
admin.site.register(ProcessLock)
|
100
104
|
admin.site.register(SpeechToTextModelOptions)
|
101
105
|
admin.site.register(ReflectiveQuestion)
|
102
|
-
admin.site.register(UserSearchModelConfig)
|
103
106
|
admin.site.register(ClientApplication)
|
104
107
|
admin.site.register(GithubConfig)
|
105
108
|
admin.site.register(NotionConfig)
|