khoj 1.24.2.dev34__py3-none-any.whl → 1.25.1.dev9__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. khoj/configure.py +13 -4
  2. khoj/database/adapters/__init__.py +54 -26
  3. khoj/interface/compiled/404/index.html +1 -1
  4. khoj/interface/compiled/_next/static/chunks/7762-79f2205740622b5c.js +1 -0
  5. khoj/interface/compiled/_next/static/chunks/app/agents/page-b406d166301c4c7d.js +1 -0
  6. khoj/interface/compiled/_next/static/chunks/app/automations/{page-1688dead2f21270d.js → page-2edc21f30819def4.js} +1 -1
  7. khoj/interface/compiled/_next/static/chunks/app/chat/{page-91abcb71846922b7.js → page-4309c98e6dc497dd.js} +1 -1
  8. khoj/interface/compiled/_next/static/chunks/app/factchecker/{page-7ab093711c27041c.js → page-f2c83e3a87a28657.js} +1 -1
  9. khoj/interface/compiled/_next/static/chunks/app/{page-fada198096eab47f.js → page-ab9beb5a26e396f7.js} +1 -1
  10. khoj/interface/compiled/_next/static/chunks/app/search/{page-a7e036689b6507ff.js → page-b807caebd7f278c7.js} +1 -1
  11. khoj/interface/compiled/_next/static/chunks/app/settings/{page-fa11cafaec7ab39f.js → page-2932356ad11c2f7b.js} +1 -1
  12. khoj/interface/compiled/_next/static/chunks/app/share/chat/{page-c5d2b9076e5390b2.js → page-a736a0826570af2b.js} +1 -1
  13. khoj/interface/compiled/_next/static/chunks/{webpack-f52083d548d804fa.js → webpack-462819dcfa6a1e2b.js} +1 -1
  14. khoj/interface/compiled/_next/static/css/3e1f1fdd70775091.css +1 -0
  15. khoj/interface/compiled/_next/static/css/b1094827d745306b.css +1 -0
  16. khoj/interface/compiled/_next/static/css/b9a6bf04305d98d7.css +25 -0
  17. khoj/interface/compiled/agents/index.html +1 -1
  18. khoj/interface/compiled/agents/index.txt +2 -2
  19. khoj/interface/compiled/automations/index.html +1 -1
  20. khoj/interface/compiled/automations/index.txt +2 -2
  21. khoj/interface/compiled/chat/index.html +1 -1
  22. khoj/interface/compiled/chat/index.txt +2 -2
  23. khoj/interface/compiled/factchecker/index.html +1 -1
  24. khoj/interface/compiled/factchecker/index.txt +2 -2
  25. khoj/interface/compiled/index.html +1 -1
  26. khoj/interface/compiled/index.txt +2 -2
  27. khoj/interface/compiled/search/index.html +1 -1
  28. khoj/interface/compiled/search/index.txt +2 -2
  29. khoj/interface/compiled/settings/index.html +1 -1
  30. khoj/interface/compiled/settings/index.txt +2 -2
  31. khoj/interface/compiled/share/chat/index.html +1 -1
  32. khoj/interface/compiled/share/chat/index.txt +2 -2
  33. khoj/processor/image/generate.py +1 -2
  34. khoj/processor/tools/online_search.py +4 -4
  35. khoj/routers/api.py +1 -1
  36. khoj/routers/api_agents.py +4 -1
  37. khoj/routers/api_chat.py +9 -11
  38. khoj/routers/api_model.py +1 -1
  39. khoj/routers/auth.py +9 -1
  40. khoj/routers/helpers.py +47 -49
  41. khoj/routers/subscription.py +18 -4
  42. khoj/utils/initialization.py +0 -3
  43. {khoj-1.24.2.dev34.dist-info → khoj-1.25.1.dev9.dist-info}/METADATA +18 -13
  44. {khoj-1.24.2.dev34.dist-info → khoj-1.25.1.dev9.dist-info}/RECORD +52 -52
  45. khoj/interface/compiled/_next/static/chunks/1269-2e52d48e7d0e5c61.js +0 -1
  46. khoj/interface/compiled/_next/static/chunks/app/agents/page-1b494bb54aca52de.js +0 -1
  47. khoj/interface/compiled/_next/static/css/592ca99f5122e75a.css +0 -1
  48. khoj/interface/compiled/_next/static/css/7a2b0a2d7c3d86eb.css +0 -25
  49. khoj/interface/compiled/_next/static/css/dfb67a9287720a2b.css +0 -1
  50. /khoj/interface/compiled/_next/static/chunks/{9178-899fe9a6b754ecfe.js → 9178-f208a3e6404714a9.js} +0 -0
  51. /khoj/interface/compiled/_next/static/chunks/{9417-29502e39c3e7d60c.js → 9417-1d158bf46d3a0dc9.js} +0 -0
  52. /khoj/interface/compiled/_next/static/chunks/{9479-7eed36fc954ef804.js → 9479-563e4d61f91d5a7c.js} +0 -0
  53. /khoj/interface/compiled/_next/static/{Be65WE5esb8ZOxD33psmy → jRL5xyceUdI0nvEyCkgqF}/_buildManifest.js +0 -0
  54. /khoj/interface/compiled/_next/static/{Be65WE5esb8ZOxD33psmy → jRL5xyceUdI0nvEyCkgqF}/_ssgManifest.js +0 -0
  55. {khoj-1.24.2.dev34.dist-info → khoj-1.25.1.dev9.dist-info}/WHEEL +0 -0
  56. {khoj-1.24.2.dev34.dist-info → khoj-1.25.1.dev9.dist-info}/entry_points.txt +0 -0
  57. {khoj-1.24.2.dev34.dist-info → khoj-1.25.1.dev9.dist-info}/licenses/LICENSE +0 -0
khoj/routers/helpers.py CHANGED
@@ -39,6 +39,7 @@ from khoj.database.adapters import (
39
39
  AutomationAdapters,
40
40
  ConversationAdapters,
41
41
  EntryAdapters,
42
+ ais_user_subscribed,
42
43
  create_khoj_token,
43
44
  get_khoj_tokens,
44
45
  get_user_name,
@@ -119,20 +120,20 @@ def is_query_empty(query: str) -> bool:
119
120
  return is_none_or_empty(query.strip())
120
121
 
121
122
 
122
- def validate_conversation_config():
123
- default_config = ConversationAdapters.get_default_conversation_config()
123
+ def validate_conversation_config(user: KhojUser):
124
+ default_config = ConversationAdapters.get_default_conversation_config(user)
124
125
 
125
126
  if default_config is None:
126
- raise HTTPException(status_code=500, detail="Contact the server administrator to set a default chat model.")
127
+ raise HTTPException(status_code=500, detail="Contact the server administrator to add a chat model.")
127
128
 
128
129
  if default_config.model_type == "openai" and not default_config.openai_config:
129
- raise HTTPException(status_code=500, detail="Contact the server administrator to set a default chat model.")
130
+ raise HTTPException(status_code=500, detail="Contact the server administrator to add a chat model.")
130
131
 
131
132
 
132
133
  async def is_ready_to_chat(user: KhojUser):
133
- user_conversation_config = (await ConversationAdapters.aget_user_conversation_config(user)) or (
134
- await ConversationAdapters.aget_default_conversation_config()
135
- )
134
+ user_conversation_config = await ConversationAdapters.aget_user_conversation_config(user)
135
+ if user_conversation_config == None:
136
+ user_conversation_config = await ConversationAdapters.aget_default_conversation_config()
136
137
 
137
138
  if user_conversation_config and user_conversation_config.model_type == ChatModelOptions.ModelType.OFFLINE:
138
139
  chat_model = user_conversation_config.chat_model
@@ -246,19 +247,19 @@ async def agenerate_chat_response(*args):
246
247
  return await loop.run_in_executor(executor, generate_chat_response, *args)
247
248
 
248
249
 
249
- async def acreate_title_from_query(query: str) -> str:
250
+ async def acreate_title_from_query(query: str, user: KhojUser = None) -> str:
250
251
  """
251
252
  Create a title from the given query
252
253
  """
253
254
  title_generation_prompt = prompts.subject_generation.format(query=query)
254
255
 
255
256
  with timer("Chat actor: Generate title from query", logger):
256
- response = await send_message_to_model_wrapper(title_generation_prompt)
257
+ response = await send_message_to_model_wrapper(title_generation_prompt, user=user)
257
258
 
258
259
  return response.strip()
259
260
 
260
261
 
261
- async def acheck_if_safe_prompt(system_prompt: str) -> Tuple[bool, str]:
262
+ async def acheck_if_safe_prompt(system_prompt: str, user: KhojUser = None) -> Tuple[bool, str]:
262
263
  """
263
264
  Check if the system prompt is safe to use
264
265
  """
@@ -267,7 +268,7 @@ async def acheck_if_safe_prompt(system_prompt: str) -> Tuple[bool, str]:
267
268
  reason = ""
268
269
 
269
270
  with timer("Chat actor: Check if safe prompt", logger):
270
- response = await send_message_to_model_wrapper(safe_prompt_check)
271
+ response = await send_message_to_model_wrapper(safe_prompt_check, user=user)
271
272
 
272
273
  response = response.strip()
273
274
  try:
@@ -288,7 +289,7 @@ async def aget_relevant_information_sources(
288
289
  query: str,
289
290
  conversation_history: dict,
290
291
  is_task: bool,
291
- subscribed: bool,
292
+ user: KhojUser,
292
293
  uploaded_image_url: str = None,
293
294
  agent: Agent = None,
294
295
  ):
@@ -326,7 +327,7 @@ async def aget_relevant_information_sources(
326
327
  response = await send_message_to_model_wrapper(
327
328
  relevant_tools_prompt,
328
329
  response_type="json_object",
329
- subscribed=subscribed,
330
+ user=user,
330
331
  )
331
332
 
332
333
  try:
@@ -362,7 +363,12 @@ async def aget_relevant_information_sources(
362
363
 
363
364
 
364
365
  async def aget_relevant_output_modes(
365
- query: str, conversation_history: dict, is_task: bool = False, uploaded_image_url: str = None, agent: Agent = None
366
+ query: str,
367
+ conversation_history: dict,
368
+ is_task: bool = False,
369
+ user: KhojUser = None,
370
+ uploaded_image_url: str = None,
371
+ agent: Agent = None,
366
372
  ):
367
373
  """
368
374
  Given a query, determine which of the available tools the agent should use in order to answer appropriately.
@@ -398,7 +404,7 @@ async def aget_relevant_output_modes(
398
404
  )
399
405
 
400
406
  with timer("Chat actor: Infer output mode for chat response", logger):
401
- response = await send_message_to_model_wrapper(relevant_mode_prompt, response_type="json_object")
407
+ response = await send_message_to_model_wrapper(relevant_mode_prompt, response_type="json_object", user=user)
402
408
 
403
409
  try:
404
410
  response = response.strip()
@@ -453,7 +459,7 @@ async def infer_webpage_urls(
453
459
 
454
460
  with timer("Chat actor: Infer webpage urls to read", logger):
455
461
  response = await send_message_to_model_wrapper(
456
- online_queries_prompt, uploaded_image_url=uploaded_image_url, response_type="json_object"
462
+ online_queries_prompt, uploaded_image_url=uploaded_image_url, response_type="json_object", user=user
457
463
  )
458
464
 
459
465
  # Validate that the response is a non-empty, JSON-serializable list of URLs
@@ -499,7 +505,7 @@ async def generate_online_subqueries(
499
505
 
500
506
  with timer("Chat actor: Generate online search subqueries", logger):
501
507
  response = await send_message_to_model_wrapper(
502
- online_queries_prompt, uploaded_image_url=uploaded_image_url, response_type="json_object"
508
+ online_queries_prompt, uploaded_image_url=uploaded_image_url, response_type="json_object", user=user
503
509
  )
504
510
 
505
511
  # Validate that the response is a non-empty, JSON-serializable list
@@ -517,7 +523,9 @@ async def generate_online_subqueries(
517
523
  return [q]
518
524
 
519
525
 
520
- async def schedule_query(q: str, conversation_history: dict, uploaded_image_url: str = None) -> Tuple[str, ...]:
526
+ async def schedule_query(
527
+ q: str, conversation_history: dict, user: KhojUser, uploaded_image_url: str = None
528
+ ) -> Tuple[str, ...]:
521
529
  """
522
530
  Schedule the date, time to run the query. Assume the server timezone is UTC.
523
531
  """
@@ -529,7 +537,7 @@ async def schedule_query(q: str, conversation_history: dict, uploaded_image_url:
529
537
  )
530
538
 
531
539
  raw_response = await send_message_to_model_wrapper(
532
- crontime_prompt, uploaded_image_url=uploaded_image_url, response_type="json_object"
540
+ crontime_prompt, uploaded_image_url=uploaded_image_url, response_type="json_object", user=user
533
541
  )
534
542
 
535
543
  # Validate that the response is a non-empty, JSON-serializable list
@@ -543,7 +551,7 @@ async def schedule_query(q: str, conversation_history: dict, uploaded_image_url:
543
551
  raise AssertionError(f"Invalid response for scheduling query: {raw_response}")
544
552
 
545
553
 
546
- async def extract_relevant_info(q: str, corpus: str, subscribed: bool, agent: Agent = None) -> Union[str, None]:
554
+ async def extract_relevant_info(q: str, corpus: str, user: KhojUser = None, agent: Agent = None) -> Union[str, None]:
547
555
  """
548
556
  Extract relevant information for a given query from the target corpus
549
557
  """
@@ -561,14 +569,11 @@ async def extract_relevant_info(q: str, corpus: str, subscribed: bool, agent: Ag
561
569
  personality_context=personality_context,
562
570
  )
563
571
 
564
- chat_model: ChatModelOptions = await ConversationAdapters.aget_default_conversation_config()
565
-
566
572
  with timer("Chat actor: Extract relevant information from data", logger):
567
573
  response = await send_message_to_model_wrapper(
568
574
  extract_relevant_information,
569
575
  prompts.system_prompt_extract_relevant_information,
570
- chat_model_option=chat_model,
571
- subscribed=subscribed,
576
+ user=user,
572
577
  )
573
578
  return response.strip()
574
579
 
@@ -577,8 +582,8 @@ async def extract_relevant_summary(
577
582
  q: str,
578
583
  corpus: str,
579
584
  conversation_history: dict,
580
- subscribed: bool = False,
581
585
  uploaded_image_url: str = None,
586
+ user: KhojUser = None,
582
587
  agent: Agent = None,
583
588
  ) -> Union[str, None]:
584
589
  """
@@ -601,14 +606,11 @@ async def extract_relevant_summary(
601
606
  personality_context=personality_context,
602
607
  )
603
608
 
604
- chat_model: ChatModelOptions = await ConversationAdapters.aget_default_conversation_config()
605
-
606
609
  with timer("Chat actor: Extract relevant information from data", logger):
607
610
  response = await send_message_to_model_wrapper(
608
611
  extract_relevant_information,
609
612
  prompts.system_prompt_extract_relevant_summary,
610
- chat_model_option=chat_model,
611
- subscribed=subscribed,
613
+ user=user,
612
614
  uploaded_image_url=uploaded_image_url,
613
615
  )
614
616
  return response.strip()
@@ -621,8 +623,8 @@ async def generate_better_image_prompt(
621
623
  note_references: List[Dict[str, Any]],
622
624
  online_results: Optional[dict] = None,
623
625
  model_type: Optional[str] = None,
624
- subscribed: bool = False,
625
626
  uploaded_image_url: Optional[str] = None,
627
+ user: KhojUser = None,
626
628
  agent: Agent = None,
627
629
  ) -> str:
628
630
  """
@@ -672,12 +674,8 @@ async def generate_better_image_prompt(
672
674
  personality_context=personality_context,
673
675
  )
674
676
 
675
- chat_model: ChatModelOptions = await ConversationAdapters.aget_default_conversation_config()
676
-
677
677
  with timer("Chat actor: Generate contextual image prompt", logger):
678
- response = await send_message_to_model_wrapper(
679
- image_prompt, chat_model_option=chat_model, subscribed=subscribed, uploaded_image_url=uploaded_image_url
680
- )
678
+ response = await send_message_to_model_wrapper(image_prompt, uploaded_image_url=uploaded_image_url, user=user)
681
679
  response = response.strip()
682
680
  if response.startswith(('"', "'")) and response.endswith(('"', "'")):
683
681
  response = response[1:-1]
@@ -689,14 +687,10 @@ async def send_message_to_model_wrapper(
689
687
  message: str,
690
688
  system_message: str = "",
691
689
  response_type: str = "text",
692
- chat_model_option: ChatModelOptions = None,
693
- subscribed: bool = False,
690
+ user: KhojUser = None,
694
691
  uploaded_image_url: str = None,
695
692
  ):
696
- conversation_config: ChatModelOptions = (
697
- chat_model_option or await ConversationAdapters.aget_default_conversation_config()
698
- )
699
-
693
+ conversation_config: ChatModelOptions = await ConversationAdapters.aget_default_conversation_config(user)
700
694
  vision_available = conversation_config.vision_enabled
701
695
  if not vision_available and uploaded_image_url:
702
696
  vision_enabled_config = await ConversationAdapters.aget_vision_enabled_config()
@@ -704,6 +698,7 @@ async def send_message_to_model_wrapper(
704
698
  conversation_config = vision_enabled_config
705
699
  vision_available = True
706
700
 
701
+ subscribed = await ais_user_subscribed(user)
707
702
  chat_model = conversation_config.chat_model
708
703
  max_tokens = (
709
704
  conversation_config.subscribed_max_prompt_size
@@ -802,8 +797,9 @@ def send_message_to_model_wrapper_sync(
802
797
  message: str,
803
798
  system_message: str = "",
804
799
  response_type: str = "text",
800
+ user: KhojUser = None,
805
801
  ):
806
- conversation_config: ChatModelOptions = ConversationAdapters.get_default_conversation_config()
802
+ conversation_config: ChatModelOptions = ConversationAdapters.get_default_conversation_config(user)
807
803
 
808
804
  if conversation_config is None:
809
805
  raise HTTPException(status_code=500, detail="Contact the server administrator to set a default chat model.")
@@ -1182,7 +1178,7 @@ class CommonQueryParamsClass:
1182
1178
  CommonQueryParams = Annotated[CommonQueryParamsClass, Depends()]
1183
1179
 
1184
1180
 
1185
- def should_notify(original_query: str, executed_query: str, ai_response: str) -> bool:
1181
+ def should_notify(original_query: str, executed_query: str, ai_response: str, user: KhojUser) -> bool:
1186
1182
  """
1187
1183
  Decide whether to notify the user of the AI response.
1188
1184
  Default to notifying the user for now.
@@ -1199,7 +1195,7 @@ def should_notify(original_query: str, executed_query: str, ai_response: str) ->
1199
1195
  with timer("Chat actor: Decide to notify user of automation response", logger):
1200
1196
  try:
1201
1197
  # TODO Replace with async call so we don't have to maintain a sync version
1202
- response = send_message_to_model_wrapper_sync(to_notify_or_not)
1198
+ response = send_message_to_model_wrapper_sync(to_notify_or_not, user)
1203
1199
  should_notify_result = "no" not in response.lower()
1204
1200
  logger.info(f'Decided to {"not " if not should_notify_result else ""}notify user of automation response.')
1205
1201
  return should_notify_result
@@ -1291,7 +1287,9 @@ def scheduled_chat(
1291
1287
  ai_response = raw_response.text
1292
1288
 
1293
1289
  # Notify user if the AI response is satisfactory
1294
- if should_notify(original_query=scheduling_request, executed_query=cleaned_query, ai_response=ai_response):
1290
+ if should_notify(
1291
+ original_query=scheduling_request, executed_query=cleaned_query, ai_response=ai_response, user=user
1292
+ ):
1295
1293
  if is_resend_enabled():
1296
1294
  send_task_email(user.get_short_name(), user.email, cleaned_query, ai_response, subject, is_image)
1297
1295
  else:
@@ -1301,7 +1299,7 @@ def scheduled_chat(
1301
1299
  async def create_automation(
1302
1300
  q: str, timezone: str, user: KhojUser, calling_url: URL, meta_log: dict = {}, conversation_id: str = None
1303
1301
  ):
1304
- crontime, query_to_run, subject = await schedule_query(q, meta_log)
1302
+ crontime, query_to_run, subject = await schedule_query(q, meta_log, user)
1305
1303
  job = await schedule_automation(query_to_run, subject, crontime, timezone, q, user, calling_url, conversation_id)
1306
1304
  return job, crontime, query_to_run, subject
1307
1305
 
@@ -1495,9 +1493,9 @@ def get_user_config(user: KhojUser, request: Request, is_detailed: bool = False)
1495
1493
  current_notion_config = get_user_notion_config(user)
1496
1494
  notion_token = current_notion_config.token if current_notion_config else ""
1497
1495
 
1498
- selected_chat_model_config = (
1499
- ConversationAdapters.get_conversation_config(user) or ConversationAdapters.get_default_conversation_config()
1500
- )
1496
+ selected_chat_model_config = ConversationAdapters.get_conversation_config(
1497
+ user
1498
+ ) or ConversationAdapters.get_default_conversation_config(user)
1501
1499
  chat_models = ConversationAdapters.get_conversation_processor_options().all()
1502
1500
  chat_model_options = list()
1503
1501
  for chat_model in chat_models:
@@ -7,6 +7,7 @@ from fastapi import APIRouter, Request
7
7
  from starlette.authentication import requires
8
8
 
9
9
  from khoj.database import adapters
10
+ from khoj.routers.helpers import update_telemetry_state
10
11
  from khoj.utils import state
11
12
 
12
13
  # Stripe integration for Khoj Cloud Subscription
@@ -48,6 +49,8 @@ async def subscribe(request: Request):
48
49
  customer_id = subscription["customer"]
49
50
  customer = stripe.Customer.retrieve(customer_id)
50
51
  customer_email = customer["email"]
52
+ user = None
53
+ is_new = False
51
54
 
52
55
  # Handle valid stripe webhook events
53
56
  success = True
@@ -55,7 +58,9 @@ async def subscribe(request: Request):
55
58
  # Mark the user as subscribed and update the next renewal date on payment
56
59
  subscription = stripe.Subscription.list(customer=customer_id).data[0]
57
60
  renewal_date = datetime.fromtimestamp(subscription["current_period_end"], tz=timezone.utc)
58
- user = await adapters.set_user_subscription(customer_email, is_recurring=True, renewal_date=renewal_date)
61
+ user, is_new = await adapters.set_user_subscription(
62
+ customer_email, is_recurring=True, renewal_date=renewal_date
63
+ )
59
64
  success = user is not None
60
65
  elif event_type in {"customer.subscription.updated"}:
61
66
  user_subscription = await sync_to_async(adapters.get_user_subscription)(customer_email)
@@ -63,15 +68,24 @@ async def subscribe(request: Request):
63
68
  if user_subscription and user_subscription.renewal_date:
64
69
  # Mark user as unsubscribed or resubscribed
65
70
  is_recurring = not subscription["cancel_at_period_end"]
66
- updated_user = await adapters.set_user_subscription(customer_email, is_recurring=is_recurring)
67
- success = updated_user is not None
71
+ user, is_new = await adapters.set_user_subscription(customer_email, is_recurring=is_recurring)
72
+ success = user is not None
68
73
  elif event_type in {"customer.subscription.deleted"}:
69
74
  # Reset the user to trial state
70
- user = await adapters.set_user_subscription(
75
+ user, is_new = await adapters.set_user_subscription(
71
76
  customer_email, is_recurring=False, renewal_date=False, type="trial"
72
77
  )
73
78
  success = user is not None
74
79
 
80
+ if user and is_new:
81
+ update_telemetry_state(
82
+ request=request,
83
+ telemetry_type="api",
84
+ api="create_user",
85
+ metadata={"user_id": str(user.user.uuid)},
86
+ )
87
+ logger.log(logging.INFO, f"🥳 New User Created: {user.user.uuid}")
88
+
75
89
  logger.info(f'Stripe subscription {event["type"]} for {customer_email}')
76
90
  return {"success": success}
77
91
 
@@ -129,9 +129,6 @@ def initialization(interactive: bool = True):
129
129
  if user_chat_model_name and ChatModelOptions.objects.filter(chat_model=user_chat_model_name).exists():
130
130
  default_chat_model_name = user_chat_model_name
131
131
 
132
- # Create a server chat settings object with the default chat model
133
- default_chat_model = ChatModelOptions.objects.filter(chat_model=default_chat_model_name).first()
134
- ServerChatSettings.objects.create(chat_default=default_chat_model)
135
132
  logger.info("🗣️ Chat model configuration complete")
136
133
 
137
134
  # Set up offline speech to text model
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: khoj
3
- Version: 1.24.2.dev34
3
+ Version: 1.25.1.dev9
4
4
  Summary: Your Second Brain
5
5
  Project-URL: Homepage, https://khoj.dev
6
6
  Project-URL: Documentation, https://docs.khoj.dev
@@ -103,14 +103,14 @@ Description-Content-Type: text/markdown
103
103
  <div align="center">
104
104
 
105
105
  [![test](https://github.com/khoj-ai/khoj/actions/workflows/test.yml/badge.svg)](https://github.com/khoj-ai/khoj/actions/workflows/test.yml)
106
- [![dockerize](https://github.com/khoj-ai/khoj/actions/workflows/dockerize.yml/badge.svg)](https://github.com/khoj-ai/khoj/pkgs/container/khoj)
106
+ [![docker](https://github.com/khoj-ai/khoj/actions/workflows/dockerize.yml/badge.svg)](https://github.com/khoj-ai/khoj/pkgs/container/khoj)
107
107
  [![pypi](https://github.com/khoj-ai/khoj/actions/workflows/pypi.yml/badge.svg)](https://pypi.org/project/khoj/)
108
- ![Discord](https://img.shields.io/discord/1112065956647284756?style=plastic&label=discord)
108
+ [![discord](https://img.shields.io/discord/1112065956647284756?style=plastic&label=discord)](https://discord.gg/BDgyabRM6e)
109
109
 
110
110
  </div>
111
111
 
112
112
  <div align="center">
113
- <b>The open-source, personal AI for your digital brain</b>
113
+ <b>Your AI second brain</b>
114
114
  </div>
115
115
 
116
116
  <br />
@@ -119,11 +119,13 @@ Description-Content-Type: text/markdown
119
119
 
120
120
  [📑 Docs](https://docs.khoj.dev)
121
121
  <span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
122
- [🏮 App](https://khoj.dev)
122
+ [🌐 Web](https://khoj.dev)
123
+ <span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
124
+ [🔥 App](https://app.khoj.dev)
123
125
  <span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
124
126
  [💬 Discord](https://discord.gg/BDgyabRM6e)
125
127
  <span>&nbsp;&nbsp;•&nbsp;&nbsp;</span>
126
- [📚 Blog](https://blog.khoj.dev)
128
+ [✍🏽 Blog](https://blog.khoj.dev)
127
129
 
128
130
  </div>
129
131
 
@@ -131,14 +133,17 @@ Description-Content-Type: text/markdown
131
133
 
132
134
  ***
133
135
 
134
- [Khoj](https://khoj.dev) is a personal, open-source AI application for you to extend your capabilities.
135
- - Share your documents to extend your digital brain.
136
- - Access the internet, getting fresh information.
137
- - You can share pdf, markdown, org-mode, notion files and github repositories.
138
- - Fast, accurate semantic search on top of your docs.
139
- - Create images, talk out loud, play your messages.
140
- - Available Desktop, Emacs, Obsidian, Web and Whatsapp.
136
+ [Khoj](https://khoj.dev) is a personal AI app to extend your capabilities. It smoothly scales up from an on-device personal AI to a cloud-scale enterprise AI.
137
+
138
+ - Chat with any local or online LLM (e.g llama3, qwen, gemma, mistral, gpt, claude, gemini).
139
+ - Get answers from the internet and your docs (including image, pdf, markdown, org-mode, word, notion files).
140
+ - Access it from your Browser, Obsidian, Emacs, Desktop, Phone or Whatsapp.
141
+ - Build agents with custom knowledge bases and tools.
142
+ - Create automations to get personal newsletters and smart notifications.
143
+ - Find relevant docs quickly and easily using our advanced semantic search.
144
+ - Generate images, talk out loud, play your messages.
141
145
  - Khoj is open-source, self-hostable. Always.
146
+ - Run it privately on [your computer](https://docs.khoj.dev/get-started/setup) or try it on our [cloud app](https://app.khoj.dev).
142
147
 
143
148
  ***
144
149