khoj 1.31.0__py3-none-any.whl → 1.31.1.dev62__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 +4 -2
- khoj/database/adapters/__init__.py +66 -57
- khoj/database/admin.py +9 -9
- khoj/database/migrations/0077_chatmodel_alter_agent_chat_model_and_more.py +62 -0
- khoj/database/migrations/0078_khojuser_email_verification_code_expiry.py +17 -0
- khoj/database/models/__init__.py +8 -7
- khoj/interface/compiled/404/index.html +1 -1
- khoj/interface/compiled/_next/static/9O0zlbSu9rZ459NKSv2aS/_buildManifest.js +1 -0
- khoj/interface/compiled/_next/static/chunks/1201-aac5b5f9a28edf09.js +1 -0
- khoj/interface/compiled/_next/static/chunks/1662-adf4c615bef2fdc2.js +1 -0
- khoj/interface/compiled/_next/static/chunks/1915-878efdc6db697d8f.js +1 -0
- khoj/interface/compiled/_next/static/chunks/2117-9886e6a0232dc093.js +2 -0
- khoj/interface/compiled/_next/static/chunks/{5538-0ea2d3944ca051e1.js → 2264-23b2c33cd8c74d07.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/2781-4f022b6e9eb6df6e.js +3 -0
- khoj/interface/compiled/_next/static/chunks/2813-f842b08bce4c61a0.js +1 -0
- khoj/interface/compiled/_next/static/chunks/3091-e0ff2288e8a29dd7.js +1 -0
- khoj/interface/compiled/_next/static/chunks/3727.dcea8f2193111552.js +1 -0
- khoj/interface/compiled/_next/static/chunks/5401-980a4f512c81232e.js +20 -0
- khoj/interface/compiled/_next/static/chunks/{1279-4cb23143aa2c0228.js → 5473-b1cf56dedac6577a.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/5477-8d032883aed8a2d2.js +1 -0
- khoj/interface/compiled/_next/static/chunks/6589-f806113de469d684.js +1 -0
- khoj/interface/compiled/_next/static/chunks/8117-2e1697b782c5f185.js +1 -0
- khoj/interface/compiled/_next/static/chunks/8407-af326f8c200e619b.js +1 -0
- khoj/interface/compiled/_next/static/chunks/8667-d3e5bc726e4ff4e3.js +1 -0
- khoj/interface/compiled/_next/static/chunks/9058-25ef3344805f06ea.js +1 -0
- khoj/interface/compiled/_next/static/chunks/9262-21c17de77aafdce8.js +1 -0
- khoj/interface/compiled/_next/static/chunks/94ca1967.1d9b42d929a1ee8c.js +1 -0
- khoj/interface/compiled/_next/static/chunks/{1210.ef7a0f9a7e43da1d.js → 9597.83583248dfbf6e73.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/964ecbae.51d6faf8801d15e6.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/_not-found/{page-cfba071f5a657256.js → page-a834eddae3e235df.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/agents/layout-e49165209d2e406c.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/agents/page-6f4ff1d32a66ed71.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/automations/{layout-7f1b79a2c67af0b4.js → layout-dce809da279a4a8a.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/automations/page-148a48ddfb2ff90d.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/chat/layout-33934fc2d6ae6838.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/chat/page-be00870a40de3a25.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/layout-30e7fda7262713ce.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/page-765292332c31523e.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/search/layout-c02531d586972d7d.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/search/{page-bd47c769b7700d1d.js → page-7af2cab294dccd81.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/settings/layout-b3f6bc6f1aa118e0.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/settings/page-6b600bf11fa89194.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/share/chat/layout-6fb51c5c80f8ec67.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/share/chat/{page-751695d28116e626.js → page-6054e88b56708f44.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/d3ac728e-44ebd2a0c99b12a0.js +1 -0
- khoj/interface/compiled/_next/static/chunks/{fd9d1056-2e6c8140e79afc3b.js → fd9d1056-4482b99a36fd1673.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/main-app-de1f09df97a3cfc7.js +1 -0
- khoj/interface/compiled/_next/static/chunks/main-db4bfac6b0a8d00b.js +1 -0
- khoj/interface/compiled/_next/static/chunks/pages/{_app-f870474a17b7f2fd.js → _app-3c9ca398d360b709.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/pages/{_error-c66a4e8afc46f17b.js → _error-cf5ca766ac8f493f.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/polyfills-42372ed130431b0a.js +1 -0
- khoj/interface/compiled/_next/static/chunks/webpack-afd772b9f7c34b3f.js +1 -0
- khoj/interface/compiled/_next/static/css/3f27c3cf45375eb5.css +1 -0
- khoj/interface/compiled/_next/static/css/65ac59e147eb2057.css +25 -0
- khoj/interface/compiled/_next/static/css/8a00c3799ec0c9f8.css +1 -0
- khoj/interface/compiled/_next/static/css/{e8fb39147bff7bb4.css → 9504108437df6804.css} +1 -1
- khoj/interface/compiled/agents/index.html +1 -1
- khoj/interface/compiled/agents/index.txt +6 -6
- khoj/interface/compiled/automations/index.html +1 -1
- khoj/interface/compiled/automations/index.txt +7 -7
- khoj/interface/compiled/chat/index.html +1 -1
- khoj/interface/compiled/chat/index.txt +6 -6
- khoj/interface/compiled/index.html +1 -1
- khoj/interface/compiled/index.txt +6 -6
- khoj/interface/compiled/search/index.html +1 -1
- khoj/interface/compiled/search/index.txt +6 -6
- khoj/interface/compiled/settings/index.html +1 -1
- khoj/interface/compiled/settings/index.txt +8 -8
- khoj/interface/compiled/share/chat/index.html +1 -1
- khoj/interface/compiled/share/chat/index.txt +6 -6
- khoj/interface/email/magic_link.html +36 -13
- khoj/main.py +1 -1
- khoj/migrations/migrate_server_pg.py +7 -7
- khoj/processor/conversation/anthropic/anthropic_chat.py +3 -3
- khoj/processor/conversation/google/gemini_chat.py +3 -3
- khoj/processor/conversation/offline/chat_model.py +12 -12
- khoj/processor/conversation/openai/gpt.py +4 -4
- khoj/processor/conversation/openai/utils.py +18 -10
- khoj/processor/conversation/utils.py +4 -4
- khoj/processor/tools/online_search.py +49 -2
- khoj/routers/api.py +22 -27
- khoj/routers/api_agents.py +4 -4
- khoj/routers/api_chat.py +19 -12
- khoj/routers/api_model.py +4 -4
- khoj/routers/auth.py +94 -7
- khoj/routers/email.py +10 -14
- khoj/routers/helpers.py +176 -134
- khoj/routers/web_client.py +1 -1
- khoj/utils/helpers.py +5 -3
- khoj/utils/initialization.py +28 -26
- {khoj-1.31.0.dist-info → khoj-1.31.1.dev62.dist-info}/METADATA +5 -5
- {khoj-1.31.0.dist-info → khoj-1.31.1.dev62.dist-info}/RECORD +96 -93
- {khoj-1.31.0.dist-info → khoj-1.31.1.dev62.dist-info}/WHEEL +1 -1
- khoj/interface/compiled/_next/static/SHDrv3iet5TKNwccvVt6m/_buildManifest.js +0 -1
- khoj/interface/compiled/_next/static/chunks/1459.690bf20e7d7b7090.js +0 -1
- khoj/interface/compiled/_next/static/chunks/1603-f8ef9930c1f4eaef.js +0 -1
- khoj/interface/compiled/_next/static/chunks/1970-1b63ac1497b03a10.js +0 -1
- khoj/interface/compiled/_next/static/chunks/2646-92ba433951d02d52.js +0 -20
- khoj/interface/compiled/_next/static/chunks/3072-be830e4f8412b9d2.js +0 -1
- khoj/interface/compiled/_next/static/chunks/3463-081c031e873b7966.js +0 -3
- khoj/interface/compiled/_next/static/chunks/3690-51312931ba1eae30.js +0 -1
- khoj/interface/compiled/_next/static/chunks/3717-b46079dbe9f55694.js +0 -1
- khoj/interface/compiled/_next/static/chunks/4200-ea75740bb3c6ae60.js +0 -1
- khoj/interface/compiled/_next/static/chunks/4504-62ac13e7d94c52f9.js +0 -1
- khoj/interface/compiled/_next/static/chunks/4602-460621c3241e0d13.js +0 -1
- khoj/interface/compiled/_next/static/chunks/5512-7cc62049bbe60e11.js +0 -1
- khoj/interface/compiled/_next/static/chunks/7023-e8de2bded4df6539.js +0 -2
- khoj/interface/compiled/_next/static/chunks/7592-a09c39a38e60634b.js +0 -1
- khoj/interface/compiled/_next/static/chunks/8423-1dda16bc56236523.js +0 -1
- khoj/interface/compiled/_next/static/chunks/94ca1967.5584df65931cfe83.js +0 -1
- khoj/interface/compiled/_next/static/chunks/964ecbae.ea4eab2a3a835ffe.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/agents/layout-1878cc328ea380bd.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/agents/page-379949e11f084cf5.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/automations/page-ca10c1cf79ae54bb.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/chat/layout-1072c3b0ab136e74.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/chat/page-8a87c5de878f4f44.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/layout-6310c57b674dd6f5.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/page-b09139cb91859cd7.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/search/layout-cae84c87073877f0.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/settings/layout-f285795bc3154b8c.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/settings/page-2a2679b6e10dbac1.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/share/chat/layout-3b0c60bc13a963db.js +0 -1
- khoj/interface/compiled/_next/static/chunks/d3ac728e-a9e3522eef9b6b28.js +0 -1
- khoj/interface/compiled/_next/static/chunks/main-1ea5c2e0fdef4626.js +0 -1
- khoj/interface/compiled/_next/static/chunks/main-app-6d6ee3495efe03d4.js +0 -1
- khoj/interface/compiled/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js +0 -1
- khoj/interface/compiled/_next/static/chunks/webpack-4bd818a6399690ae.js +0 -1
- khoj/interface/compiled/_next/static/css/1f293605f2871853.css +0 -1
- khoj/interface/compiled/_next/static/css/592ca99f5122e75a.css +0 -1
- khoj/interface/compiled/_next/static/css/fd628f01a581ec3c.css +0 -25
- /khoj/interface/compiled/_next/static/{SHDrv3iet5TKNwccvVt6m → 9O0zlbSu9rZ459NKSv2aS}/_ssgManifest.js +0 -0
- {khoj-1.31.0.dist-info → khoj-1.31.1.dev62.dist-info}/entry_points.txt +0 -0
- {khoj-1.31.0.dist-info → khoj-1.31.1.dev62.dist-info}/licenses/LICENSE +0 -0
khoj/routers/auth.py
CHANGED
@@ -4,7 +4,8 @@ import logging
|
|
4
4
|
import os
|
5
5
|
from typing import Optional
|
6
6
|
|
7
|
-
|
7
|
+
import requests
|
8
|
+
from fastapi import APIRouter, Depends
|
8
9
|
from pydantic import BaseModel, EmailStr
|
9
10
|
from starlette.authentication import requires
|
10
11
|
from starlette.config import Config
|
@@ -21,7 +22,11 @@ from khoj.database.adapters import (
|
|
21
22
|
get_or_create_user,
|
22
23
|
)
|
23
24
|
from khoj.routers.email import send_magic_link_email, send_welcome_email
|
24
|
-
from khoj.routers.helpers import
|
25
|
+
from khoj.routers.helpers import (
|
26
|
+
EmailVerificationApiRateLimiter,
|
27
|
+
get_next_url,
|
28
|
+
update_telemetry_state,
|
29
|
+
)
|
25
30
|
from khoj.utils import state
|
26
31
|
|
27
32
|
logger = logging.getLogger(__name__)
|
@@ -98,16 +103,28 @@ async def login_magic_link(request: Request, form: MagicLinkForm):
|
|
98
103
|
|
99
104
|
|
100
105
|
@auth_router.get("/magic")
|
101
|
-
async def sign_in_with_magic_link(
|
102
|
-
|
106
|
+
async def sign_in_with_magic_link(
|
107
|
+
request: Request,
|
108
|
+
code: str,
|
109
|
+
email: str,
|
110
|
+
rate_limiter=Depends(
|
111
|
+
EmailVerificationApiRateLimiter(requests=10, window=60 * 60 * 24, slug="magic_link_verification")
|
112
|
+
),
|
113
|
+
):
|
114
|
+
user, code_is_expired = await aget_user_validated_by_email_verification_code(code, email)
|
115
|
+
|
103
116
|
if user:
|
117
|
+
if code_is_expired:
|
118
|
+
request.session["user"] = {}
|
119
|
+
return Response(status_code=403)
|
120
|
+
|
104
121
|
id_info = {
|
105
122
|
"email": user.email,
|
106
123
|
}
|
107
124
|
|
108
125
|
request.session["user"] = dict(id_info)
|
109
126
|
return RedirectResponse(url="/")
|
110
|
-
return
|
127
|
+
return Response(status_code=401)
|
111
128
|
|
112
129
|
|
113
130
|
@auth_router.post("/token")
|
@@ -140,11 +157,12 @@ async def delete_token(request: Request, token: str):
|
|
140
157
|
|
141
158
|
|
142
159
|
@auth_router.post("/redirect")
|
143
|
-
async def
|
160
|
+
async def auth_post(request: Request):
|
161
|
+
# This is maintained for compatibility with the /login endpoint
|
144
162
|
form = await request.form()
|
145
163
|
next_url = get_next_url(request)
|
146
164
|
for q in request.query_params:
|
147
|
-
if
|
165
|
+
if q != "next":
|
148
166
|
next_url += f"&{q}={request.query_params[q]}"
|
149
167
|
|
150
168
|
credential = form.get("credential")
|
@@ -183,7 +201,76 @@ async def auth(request: Request):
|
|
183
201
|
return RedirectResponse(url=next_url, status_code=HTTP_302_FOUND)
|
184
202
|
|
185
203
|
|
204
|
+
@auth_router.get("/redirect")
|
205
|
+
async def auth(request: Request):
|
206
|
+
next_url = get_next_url(request)
|
207
|
+
for q in request.query_params:
|
208
|
+
if q in ["code", "state", "scope", "authuser", "prompt", "session_state", "access_type"]:
|
209
|
+
continue
|
210
|
+
if q != "next":
|
211
|
+
next_url += f"&{q}={request.query_params[q]}"
|
212
|
+
|
213
|
+
code = request.query_params.get("code")
|
214
|
+
|
215
|
+
# 1. Construct the full redirect URI including domain
|
216
|
+
base_url = str(request.base_url).rstrip("/")
|
217
|
+
redirect_uri = f"{base_url}{request.app.url_path_for('auth')}"
|
218
|
+
|
219
|
+
verified_data = requests.post(
|
220
|
+
"https://oauth2.googleapis.com/token",
|
221
|
+
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
222
|
+
data={
|
223
|
+
"code": code,
|
224
|
+
"client_id": os.environ["GOOGLE_CLIENT_ID"],
|
225
|
+
"client_secret": os.environ["GOOGLE_CLIENT_SECRET"],
|
226
|
+
"redirect_uri": redirect_uri,
|
227
|
+
"grant_type": "authorization_code",
|
228
|
+
},
|
229
|
+
)
|
230
|
+
|
231
|
+
verified_data.raise_for_status()
|
232
|
+
|
233
|
+
credential = verified_data.json().get("id_token")
|
234
|
+
|
235
|
+
if not credential:
|
236
|
+
logger.error("Missing id_token in OAuth response")
|
237
|
+
return RedirectResponse(url="/login?error=invalid_token", status_code=HTTP_302_FOUND)
|
238
|
+
|
239
|
+
try:
|
240
|
+
idinfo = id_token.verify_oauth2_token(credential, google_requests.Request(), os.environ["GOOGLE_CLIENT_ID"])
|
241
|
+
except OAuthError as error:
|
242
|
+
return HTMLResponse(f"<h1>{error.error}</h1>")
|
243
|
+
khoj_user = await get_or_create_user(idinfo)
|
244
|
+
|
245
|
+
if khoj_user:
|
246
|
+
request.session["user"] = dict(idinfo)
|
247
|
+
|
248
|
+
if datetime.timedelta(minutes=3) > (datetime.datetime.now(datetime.timezone.utc) - khoj_user.date_joined):
|
249
|
+
asyncio.create_task(send_welcome_email(idinfo["name"], idinfo["email"]))
|
250
|
+
update_telemetry_state(
|
251
|
+
request=request,
|
252
|
+
telemetry_type="api",
|
253
|
+
api="create_user__google",
|
254
|
+
metadata={"server_id": str(khoj_user.uuid)},
|
255
|
+
)
|
256
|
+
logger.log(logging.INFO, f"🥳 New User Created: {khoj_user.uuid}")
|
257
|
+
|
258
|
+
return RedirectResponse(url=next_url, status_code=HTTP_302_FOUND)
|
259
|
+
|
260
|
+
|
186
261
|
@auth_router.get("/logout")
|
187
262
|
async def logout(request: Request):
|
188
263
|
request.session.pop("user", None)
|
189
264
|
return RedirectResponse(url="/")
|
265
|
+
|
266
|
+
|
267
|
+
@auth_router.get("/oauth/metadata")
|
268
|
+
async def oauth_metadata(request: Request):
|
269
|
+
redirect_uri = str(request.app.url_path_for("auth"))
|
270
|
+
|
271
|
+
return {
|
272
|
+
"google": {
|
273
|
+
"client_id": os.environ.get("GOOGLE_CLIENT_ID"),
|
274
|
+
"redirect_uri": f"{redirect_uri}",
|
275
|
+
}
|
276
|
+
}
|
khoj/routers/email.py
CHANGED
@@ -1,12 +1,8 @@
|
|
1
1
|
import logging
|
2
2
|
import os
|
3
3
|
|
4
|
-
try:
|
5
|
-
import resend
|
6
|
-
except ImportError:
|
7
|
-
pass
|
8
|
-
|
9
4
|
import markdown_it
|
5
|
+
import resend
|
10
6
|
from django.conf import settings
|
11
7
|
from jinja2 import Environment, FileSystemLoader
|
12
8
|
|
@@ -23,7 +19,7 @@ static_files = os.path.join(settings.BASE_DIR, "static")
|
|
23
19
|
env = Environment(loader=FileSystemLoader(static_files))
|
24
20
|
|
25
21
|
if not RESEND_API_KEY:
|
26
|
-
logger.
|
22
|
+
logger.warning("RESEND_API_KEY not set - email sending disabled")
|
27
23
|
else:
|
28
24
|
resend.api_key = RESEND_API_KEY
|
29
25
|
|
@@ -33,7 +29,7 @@ def is_resend_enabled():
|
|
33
29
|
|
34
30
|
|
35
31
|
async def send_magic_link_email(email, unique_id, host):
|
36
|
-
sign_in_link = f"{host}auth/magic?code={unique_id}"
|
32
|
+
sign_in_link = f"{host}auth/magic?code={unique_id}&email={email}"
|
37
33
|
|
38
34
|
if not is_resend_enabled():
|
39
35
|
logger.debug(f"Email sending disabled. Share this sign-in link with the user: {sign_in_link}")
|
@@ -41,13 +37,13 @@ async def send_magic_link_email(email, unique_id, host):
|
|
41
37
|
|
42
38
|
template = env.get_template("magic_link.html")
|
43
39
|
|
44
|
-
html_content = template.render(link=f"{host}auth/magic?code={unique_id}")
|
40
|
+
html_content = template.render(link=f"{host}auth/magic?code={unique_id}", code=unique_id)
|
45
41
|
|
46
42
|
resend.Emails.send(
|
47
43
|
{
|
48
44
|
"sender": os.environ.get("RESEND_EMAIL", "noreply@khoj.dev"),
|
49
45
|
"to": email,
|
50
|
-
"subject": "Your
|
46
|
+
"subject": f"Your login code to Khoj",
|
51
47
|
"html": html_content,
|
52
48
|
}
|
53
49
|
)
|
@@ -64,7 +60,7 @@ async def send_welcome_email(name, email):
|
|
64
60
|
|
65
61
|
resend.Emails.send(
|
66
62
|
{
|
67
|
-
"sender": "team@khoj.dev",
|
63
|
+
"sender": os.environ.get("RESEND_EMAIL", "team@khoj.dev"),
|
68
64
|
"to": email,
|
69
65
|
"subject": f"{name}, four ways to use Khoj" if name else "Four ways to use Khoj",
|
70
66
|
"html": html_content,
|
@@ -92,7 +88,7 @@ async def send_query_feedback(uquery, kquery, sentiment, user_email):
|
|
92
88
|
|
93
89
|
logger.info(f"Sending feedback email for query {uquery}")
|
94
90
|
|
95
|
-
#
|
91
|
+
# render feedback email using feedback.html as template
|
96
92
|
template = env.get_template("feedback.html")
|
97
93
|
html_content = template.render(
|
98
94
|
uquery=uquery if not is_none_or_empty(uquery) else "N/A",
|
@@ -100,10 +96,10 @@ async def send_query_feedback(uquery, kquery, sentiment, user_email):
|
|
100
96
|
sentiment=sentiment if not is_none_or_empty(sentiment) else "N/A",
|
101
97
|
user_email=user_email if not is_none_or_empty(user_email) else "N/A",
|
102
98
|
)
|
103
|
-
# send feedback
|
99
|
+
# send feedback to fixed account
|
104
100
|
r = resend.Emails.send(
|
105
101
|
{
|
106
|
-
"sender": "
|
102
|
+
"sender": os.environ.get("RESEND_EMAIL", "noreply@khoj.dev"),
|
107
103
|
"to": "team@khoj.dev",
|
108
104
|
"subject": f"User Feedback",
|
109
105
|
"html": html_content,
|
@@ -130,7 +126,7 @@ def send_task_email(name, email, query, result, subject, is_image=False):
|
|
130
126
|
|
131
127
|
r = resend.Emails.send(
|
132
128
|
{
|
133
|
-
"sender":
|
129
|
+
"sender": f'Khoj <{os.environ.get("RESEND_EMAIL", "khoj@khoj.dev")}>',
|
134
130
|
"to": email,
|
135
131
|
"subject": f"✨ {subject}",
|
136
132
|
"html": html_content,
|