khoj 1.26.4.dev2__py3-none-any.whl → 1.26.5.dev29__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 (84) hide show
  1. khoj/interface/compiled/404/index.html +1 -1
  2. khoj/interface/compiled/_next/static/chunks/1210.132a7e1910006bbb.js +1 -0
  3. khoj/interface/compiled/_next/static/chunks/1279-f37ee4a388ebf544.js +1 -0
  4. khoj/interface/compiled/_next/static/chunks/1459.690bf20e7d7b7090.js +1 -0
  5. khoj/interface/compiled/_next/static/chunks/1603-b9d95833e0e025e8.js +1 -0
  6. khoj/interface/compiled/_next/static/chunks/1970-1d6d0c1b00b4f343.js +1 -0
  7. khoj/interface/compiled/_next/static/chunks/2697-61fcba89fd87eab4.js +1 -0
  8. khoj/interface/compiled/_next/static/chunks/3423-aad88d6c1f029135.js +1 -0
  9. khoj/interface/compiled/_next/static/chunks/394-6bcb8c429f168f21.js +3 -0
  10. khoj/interface/compiled/_next/static/chunks/4602-8eeb4b76385ad159.js +1 -0
  11. khoj/interface/compiled/_next/static/chunks/5512-94c7c2bbcf58c19d.js +1 -0
  12. khoj/interface/compiled/_next/static/chunks/7113-f2e114d7034a0835.js +1 -0
  13. khoj/interface/compiled/_next/static/chunks/{4086-2c74808ba38a5a0f.js → 8840-b8d7b9f0923c6651.js} +1 -1
  14. khoj/interface/compiled/_next/static/chunks/9417-759984ad62caa3dc.js +1 -0
  15. khoj/interface/compiled/_next/static/chunks/9479-4b443fdcc99141c9.js +1 -0
  16. khoj/interface/compiled/_next/static/chunks/94ca1967.5584df65931cfe83.js +1 -0
  17. khoj/interface/compiled/_next/static/chunks/964ecbae.ea4eab2a3a835ffe.js +1 -0
  18. khoj/interface/compiled/_next/static/chunks/app/agents/page-5ae1e540bb5be8a9.js +1 -0
  19. khoj/interface/compiled/_next/static/chunks/app/automations/{page-5480731341f34450.js → page-774ae3e033f938cd.js} +1 -1
  20. khoj/interface/compiled/_next/static/chunks/app/chat/page-97f5b61aaf46d364.js +1 -0
  21. khoj/interface/compiled/_next/static/chunks/app/factchecker/{page-e7b34316ec6f44de.js → page-d82403db2866bad8.js} +1 -1
  22. khoj/interface/compiled/_next/static/chunks/app/{page-10a5aad6e04f3cf8.js → page-75bbfb564884054b.js} +1 -1
  23. khoj/interface/compiled/_next/static/chunks/app/search/{page-d56541c746fded7d.js → page-9b64f61caa5bd7f9.js} +1 -1
  24. khoj/interface/compiled/_next/static/chunks/app/settings/{page-e044a999468a7c5d.js → page-989cf38b87b19427.js} +1 -1
  25. khoj/interface/compiled/_next/static/chunks/app/share/chat/page-eb9e282691858f2e.js +1 -0
  26. khoj/interface/compiled/_next/static/chunks/webpack-8f4afe09848e24e1.js +1 -0
  27. khoj/interface/compiled/_next/static/css/{c808691c459e3887.css → 3cf13271869a4aeb.css} +1 -1
  28. khoj/interface/compiled/_next/static/css/4cae6c0e5c72fb2d.css +1 -0
  29. khoj/interface/compiled/_next/static/css/76d55eb435962b19.css +25 -0
  30. khoj/interface/compiled/_next/static/css/{3e1f1fdd70775091.css → 80bd6301fc657983.css} +1 -1
  31. khoj/interface/compiled/_next/static/css/ddcc0cf73e062476.css +1 -0
  32. khoj/interface/compiled/agents/index.html +1 -1
  33. khoj/interface/compiled/agents/index.txt +2 -2
  34. khoj/interface/compiled/automations/index.html +1 -1
  35. khoj/interface/compiled/automations/index.txt +2 -2
  36. khoj/interface/compiled/chat/index.html +1 -1
  37. khoj/interface/compiled/chat/index.txt +2 -2
  38. khoj/interface/compiled/factchecker/index.html +1 -1
  39. khoj/interface/compiled/factchecker/index.txt +2 -2
  40. khoj/interface/compiled/index.html +1 -1
  41. khoj/interface/compiled/index.txt +2 -2
  42. khoj/interface/compiled/search/index.html +1 -1
  43. khoj/interface/compiled/search/index.txt +2 -2
  44. khoj/interface/compiled/settings/index.html +1 -1
  45. khoj/interface/compiled/settings/index.txt +2 -2
  46. khoj/interface/compiled/share/chat/index.html +1 -1
  47. khoj/interface/compiled/share/chat/index.txt +2 -2
  48. khoj/processor/conversation/google/gemini_chat.py +28 -13
  49. khoj/processor/conversation/google/utils.py +34 -12
  50. khoj/processor/conversation/openai/gpt.py +4 -4
  51. khoj/processor/conversation/prompts.py +144 -0
  52. khoj/processor/conversation/utils.py +22 -13
  53. khoj/processor/image/generate.py +5 -5
  54. khoj/processor/tools/online_search.py +4 -4
  55. khoj/routers/api.py +4 -2
  56. khoj/routers/api_agents.py +41 -20
  57. khoj/routers/api_chat.py +85 -46
  58. khoj/routers/helpers.py +225 -29
  59. khoj/routers/web_client.py +0 -11
  60. khoj/utils/helpers.py +7 -3
  61. {khoj-1.26.4.dev2.dist-info → khoj-1.26.5.dev29.dist-info}/METADATA +1 -1
  62. {khoj-1.26.4.dev2.dist-info → khoj-1.26.5.dev29.dist-info}/RECORD +67 -62
  63. khoj/interface/compiled/_next/static/chunks/121-7024f479c297aef0.js +0 -1
  64. khoj/interface/compiled/_next/static/chunks/1603-fa3ee48860b9dc5c.js +0 -1
  65. khoj/interface/compiled/_next/static/chunks/2697-a38d01981ad3bdf8.js +0 -1
  66. khoj/interface/compiled/_next/static/chunks/4051-2cf66369d6ca0f1d.js +0 -3
  67. khoj/interface/compiled/_next/static/chunks/477-ec86e93db10571c1.js +0 -1
  68. khoj/interface/compiled/_next/static/chunks/51-e8f5bdb69b5ea421.js +0 -1
  69. khoj/interface/compiled/_next/static/chunks/7762-79f2205740622b5c.js +0 -1
  70. khoj/interface/compiled/_next/static/chunks/9178-899fe9a6b754ecfe.js +0 -1
  71. khoj/interface/compiled/_next/static/chunks/9417-46ed3aaa639c85ef.js +0 -1
  72. khoj/interface/compiled/_next/static/chunks/9479-ea776e73f549090c.js +0 -1
  73. khoj/interface/compiled/_next/static/chunks/app/agents/page-88aa3042711107b7.js +0 -1
  74. khoj/interface/compiled/_next/static/chunks/app/chat/page-702057ccbcf27881.js +0 -1
  75. khoj/interface/compiled/_next/static/chunks/app/share/chat/page-fbbd66a4d4633438.js +0 -1
  76. khoj/interface/compiled/_next/static/chunks/webpack-2651a68f46ac3cb7.js +0 -1
  77. khoj/interface/compiled/_next/static/css/2de69f0be774c768.css +0 -1
  78. khoj/interface/compiled/_next/static/css/592ca99f5122e75a.css +0 -1
  79. khoj/interface/compiled/_next/static/css/b9a6bf04305d98d7.css +0 -25
  80. /khoj/interface/compiled/_next/static/{wyjqS7cuSX-u62BTNYqhU → ZLHCGFLxZSUj0jEJSc99T}/_buildManifest.js +0 -0
  81. /khoj/interface/compiled/_next/static/{wyjqS7cuSX-u62BTNYqhU → ZLHCGFLxZSUj0jEJSc99T}/_ssgManifest.js +0 -0
  82. {khoj-1.26.4.dev2.dist-info → khoj-1.26.5.dev29.dist-info}/WHEEL +0 -0
  83. {khoj-1.26.4.dev2.dist-info → khoj-1.26.5.dev29.dist-info}/entry_points.txt +0 -0
  84. {khoj-1.26.4.dev2.dist-info → khoj-1.26.5.dev29.dist-info}/licenses/LICENSE +0 -0
khoj/routers/helpers.py CHANGED
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import base64
2
3
  import hashlib
3
4
  import json
4
5
  import logging
@@ -14,6 +15,7 @@ from typing import (
14
15
  Annotated,
15
16
  Any,
16
17
  AsyncGenerator,
18
+ Callable,
17
19
  Dict,
18
20
  Iterator,
19
21
  List,
@@ -21,7 +23,7 @@ from typing import (
21
23
  Tuple,
22
24
  Union,
23
25
  )
24
- from urllib.parse import parse_qs, quote, urljoin, urlparse
26
+ from urllib.parse import parse_qs, quote, unquote, urljoin, urlparse
25
27
 
26
28
  import cron_descriptor
27
29
  import pytz
@@ -30,6 +32,7 @@ from apscheduler.job import Job
30
32
  from apscheduler.triggers.cron import CronTrigger
31
33
  from asgiref.sync import sync_to_async
32
34
  from fastapi import Depends, Header, HTTPException, Request, UploadFile
35
+ from pydantic import BaseModel
33
36
  from starlette.authentication import has_required_scope
34
37
  from starlette.requests import URL
35
38
 
@@ -215,6 +218,9 @@ def construct_chat_history(conversation_history: dict, n: int = 4, agent_name="A
215
218
  elif chat["by"] == "khoj" and ("text-to-image" in chat["intent"].get("type")):
216
219
  chat_history += f"User: {chat['intent']['query']}\n"
217
220
  chat_history += f"{agent_name}: [generated image redacted for space]\n"
221
+ elif chat["by"] == "khoj" and ("excalidraw" in chat["intent"].get("type")):
222
+ chat_history += f"User: {chat['intent']['query']}\n"
223
+ chat_history += f"{agent_name}: {chat['intent']['inferred-queries'][0]}\n"
218
224
  return chat_history
219
225
 
220
226
 
@@ -235,6 +241,8 @@ def get_conversation_command(query: str, any_references: bool = False) -> Conver
235
241
  return ConversationCommand.AutomatedTask
236
242
  elif query.startswith("/summarize"):
237
243
  return ConversationCommand.Summarize
244
+ elif query.startswith("/diagram"):
245
+ return ConversationCommand.Diagram
238
246
  # If no relevant notes found for the given query
239
247
  elif not any_references:
240
248
  return ConversationCommand.General
@@ -290,7 +298,7 @@ async def aget_relevant_information_sources(
290
298
  conversation_history: dict,
291
299
  is_task: bool,
292
300
  user: KhojUser,
293
- uploaded_image_url: str = None,
301
+ query_images: List[str] = None,
294
302
  agent: Agent = None,
295
303
  ):
296
304
  """
@@ -309,8 +317,8 @@ async def aget_relevant_information_sources(
309
317
 
310
318
  chat_history = construct_chat_history(conversation_history)
311
319
 
312
- if uploaded_image_url:
313
- query = f"[placeholder for user attached image]\n{query}"
320
+ if query_images:
321
+ query = f"[placeholder for {len(query_images)} user attached images]\n{query}"
314
322
 
315
323
  personality_context = (
316
324
  prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else ""
@@ -367,7 +375,7 @@ async def aget_relevant_output_modes(
367
375
  conversation_history: dict,
368
376
  is_task: bool = False,
369
377
  user: KhojUser = None,
370
- uploaded_image_url: str = None,
378
+ query_images: List[str] = None,
371
379
  agent: Agent = None,
372
380
  ):
373
381
  """
@@ -389,8 +397,8 @@ async def aget_relevant_output_modes(
389
397
 
390
398
  chat_history = construct_chat_history(conversation_history)
391
399
 
392
- if uploaded_image_url:
393
- query = f"[placeholder for user attached image]\n{query}"
400
+ if query_images:
401
+ query = f"[placeholder for {len(query_images)} user attached images]\n{query}"
394
402
 
395
403
  personality_context = (
396
404
  prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else ""
@@ -433,7 +441,7 @@ async def infer_webpage_urls(
433
441
  conversation_history: dict,
434
442
  location_data: LocationData,
435
443
  user: KhojUser,
436
- uploaded_image_url: str = None,
444
+ query_images: List[str] = None,
437
445
  agent: Agent = None,
438
446
  ) -> List[str]:
439
447
  """
@@ -459,7 +467,7 @@ async def infer_webpage_urls(
459
467
 
460
468
  with timer("Chat actor: Infer webpage urls to read", logger):
461
469
  response = await send_message_to_model_wrapper(
462
- online_queries_prompt, uploaded_image_url=uploaded_image_url, response_type="json_object", user=user
470
+ online_queries_prompt, query_images=query_images, response_type="json_object", user=user
463
471
  )
464
472
 
465
473
  # Validate that the response is a non-empty, JSON-serializable list of URLs
@@ -479,7 +487,7 @@ async def generate_online_subqueries(
479
487
  conversation_history: dict,
480
488
  location_data: LocationData,
481
489
  user: KhojUser,
482
- uploaded_image_url: str = None,
490
+ query_images: List[str] = None,
483
491
  agent: Agent = None,
484
492
  ) -> List[str]:
485
493
  """
@@ -505,7 +513,7 @@ async def generate_online_subqueries(
505
513
 
506
514
  with timer("Chat actor: Generate online search subqueries", logger):
507
515
  response = await send_message_to_model_wrapper(
508
- online_queries_prompt, uploaded_image_url=uploaded_image_url, response_type="json_object", user=user
516
+ online_queries_prompt, query_images=query_images, response_type="json_object", user=user
509
517
  )
510
518
 
511
519
  # Validate that the response is a non-empty, JSON-serializable list
@@ -524,7 +532,7 @@ async def generate_online_subqueries(
524
532
 
525
533
 
526
534
  async def schedule_query(
527
- q: str, conversation_history: dict, user: KhojUser, uploaded_image_url: str = None
535
+ q: str, conversation_history: dict, user: KhojUser, query_images: List[str] = None
528
536
  ) -> Tuple[str, ...]:
529
537
  """
530
538
  Schedule the date, time to run the query. Assume the server timezone is UTC.
@@ -537,7 +545,7 @@ async def schedule_query(
537
545
  )
538
546
 
539
547
  raw_response = await send_message_to_model_wrapper(
540
- crontime_prompt, uploaded_image_url=uploaded_image_url, response_type="json_object", user=user
548
+ crontime_prompt, query_images=query_images, response_type="json_object", user=user
541
549
  )
542
550
 
543
551
  # Validate that the response is a non-empty, JSON-serializable list
@@ -583,7 +591,7 @@ async def extract_relevant_summary(
583
591
  q: str,
584
592
  corpus: str,
585
593
  conversation_history: dict,
586
- uploaded_image_url: str = None,
594
+ query_images: List[str] = None,
587
595
  user: KhojUser = None,
588
596
  agent: Agent = None,
589
597
  ) -> Union[str, None]:
@@ -612,11 +620,134 @@ async def extract_relevant_summary(
612
620
  extract_relevant_information,
613
621
  prompts.system_prompt_extract_relevant_summary,
614
622
  user=user,
615
- uploaded_image_url=uploaded_image_url,
623
+ query_images=query_images,
616
624
  )
617
625
  return response.strip()
618
626
 
619
627
 
628
+ async def generate_excalidraw_diagram(
629
+ q: str,
630
+ conversation_history: Dict[str, Any],
631
+ location_data: LocationData,
632
+ note_references: List[Dict[str, Any]],
633
+ online_results: Optional[dict] = None,
634
+ query_images: List[str] = None,
635
+ user: KhojUser = None,
636
+ agent: Agent = None,
637
+ send_status_func: Optional[Callable] = None,
638
+ ):
639
+ if send_status_func:
640
+ async for event in send_status_func("**Enhancing the Diagramming Prompt**"):
641
+ yield {ChatEvent.STATUS: event}
642
+
643
+ better_diagram_description_prompt = await generate_better_diagram_description(
644
+ q=q,
645
+ conversation_history=conversation_history,
646
+ location_data=location_data,
647
+ note_references=note_references,
648
+ online_results=online_results,
649
+ query_images=query_images,
650
+ user=user,
651
+ agent=agent,
652
+ )
653
+
654
+ if send_status_func:
655
+ async for event in send_status_func(f"**Diagram to Create:**:\n{better_diagram_description_prompt}"):
656
+ yield {ChatEvent.STATUS: event}
657
+
658
+ excalidraw_diagram_description = await generate_excalidraw_diagram_from_description(
659
+ q=better_diagram_description_prompt,
660
+ user=user,
661
+ agent=agent,
662
+ )
663
+
664
+ yield better_diagram_description_prompt, excalidraw_diagram_description
665
+
666
+
667
+ async def generate_better_diagram_description(
668
+ q: str,
669
+ conversation_history: Dict[str, Any],
670
+ location_data: LocationData,
671
+ note_references: List[Dict[str, Any]],
672
+ online_results: Optional[dict] = None,
673
+ query_images: List[str] = None,
674
+ user: KhojUser = None,
675
+ agent: Agent = None,
676
+ ) -> str:
677
+ """
678
+ Generate a diagram description from the given query and context
679
+ """
680
+
681
+ today_date = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d, %A")
682
+ personality_context = (
683
+ prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else ""
684
+ )
685
+
686
+ if location_data:
687
+ location_prompt = prompts.user_location.format(location=f"{location_data}")
688
+ else:
689
+ location_prompt = "Unknown"
690
+
691
+ user_references = "\n\n".join([f"# {item['compiled']}" for item in note_references])
692
+
693
+ chat_history = construct_chat_history(conversation_history)
694
+
695
+ simplified_online_results = {}
696
+
697
+ if online_results:
698
+ for result in online_results:
699
+ if online_results[result].get("answerBox"):
700
+ simplified_online_results[result] = online_results[result]["answerBox"]
701
+ elif online_results[result].get("webpages"):
702
+ simplified_online_results[result] = online_results[result]["webpages"]
703
+
704
+ improve_diagram_description_prompt = prompts.improve_diagram_description_prompt.format(
705
+ query=q,
706
+ chat_history=chat_history,
707
+ location=location_prompt,
708
+ current_date=today_date,
709
+ references=user_references,
710
+ online_results=simplified_online_results,
711
+ personality_context=personality_context,
712
+ )
713
+
714
+ with timer("Chat actor: Generate better diagram description", logger):
715
+ response = await send_message_to_model_wrapper(
716
+ improve_diagram_description_prompt, query_images=query_images, user=user
717
+ )
718
+ response = response.strip()
719
+ if response.startswith(('"', "'")) and response.endswith(('"', "'")):
720
+ response = response[1:-1]
721
+
722
+ return response
723
+
724
+
725
+ async def generate_excalidraw_diagram_from_description(
726
+ q: str,
727
+ user: KhojUser = None,
728
+ agent: Agent = None,
729
+ ) -> str:
730
+ personality_context = (
731
+ prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else ""
732
+ )
733
+
734
+ excalidraw_diagram_generation = prompts.excalidraw_diagram_generation_prompt.format(
735
+ personality_context=personality_context,
736
+ query=q,
737
+ )
738
+
739
+ with timer("Chat actor: Generate excalidraw diagram", logger):
740
+ raw_response = await send_message_to_model_wrapper(message=excalidraw_diagram_generation, user=user)
741
+ raw_response = raw_response.strip()
742
+ raw_response = remove_json_codeblock(raw_response)
743
+ response: Dict[str, str] = json.loads(raw_response)
744
+ if not response or not isinstance(response, List) or not isinstance(response[0], Dict):
745
+ # TODO Some additional validation here that it's a valid Excalidraw diagram
746
+ raise AssertionError(f"Invalid response for improving diagram description: {response}")
747
+
748
+ return response
749
+
750
+
620
751
  async def generate_better_image_prompt(
621
752
  q: str,
622
753
  conversation_history: str,
@@ -624,7 +755,7 @@ async def generate_better_image_prompt(
624
755
  note_references: List[Dict[str, Any]],
625
756
  online_results: Optional[dict] = None,
626
757
  model_type: Optional[str] = None,
627
- uploaded_image_url: Optional[str] = None,
758
+ query_images: Optional[List[str]] = None,
628
759
  user: KhojUser = None,
629
760
  agent: Agent = None,
630
761
  ) -> str:
@@ -676,7 +807,7 @@ async def generate_better_image_prompt(
676
807
  )
677
808
 
678
809
  with timer("Chat actor: Generate contextual image prompt", logger):
679
- response = await send_message_to_model_wrapper(image_prompt, uploaded_image_url=uploaded_image_url, user=user)
810
+ response = await send_message_to_model_wrapper(image_prompt, query_images=query_images, user=user)
680
811
  response = response.strip()
681
812
  if response.startswith(('"', "'")) and response.endswith(('"', "'")):
682
813
  response = response[1:-1]
@@ -689,11 +820,11 @@ async def send_message_to_model_wrapper(
689
820
  system_message: str = "",
690
821
  response_type: str = "text",
691
822
  user: KhojUser = None,
692
- uploaded_image_url: str = None,
823
+ query_images: List[str] = None,
693
824
  ):
694
825
  conversation_config: ChatModelOptions = await ConversationAdapters.aget_default_conversation_config(user)
695
826
  vision_available = conversation_config.vision_enabled
696
- if not vision_available and uploaded_image_url:
827
+ if not vision_available and query_images:
697
828
  vision_enabled_config = await ConversationAdapters.aget_vision_enabled_config()
698
829
  if vision_enabled_config:
699
830
  conversation_config = vision_enabled_config
@@ -746,7 +877,7 @@ async def send_message_to_model_wrapper(
746
877
  max_prompt_size=max_tokens,
747
878
  tokenizer_name=tokenizer,
748
879
  vision_enabled=vision_available,
749
- uploaded_image_url=uploaded_image_url,
880
+ query_images=query_images,
750
881
  model_type=conversation_config.model_type,
751
882
  )
752
883
 
@@ -766,7 +897,7 @@ async def send_message_to_model_wrapper(
766
897
  max_prompt_size=max_tokens,
767
898
  tokenizer_name=tokenizer,
768
899
  vision_enabled=vision_available,
769
- uploaded_image_url=uploaded_image_url,
900
+ query_images=query_images,
770
901
  model_type=conversation_config.model_type,
771
902
  )
772
903
 
@@ -784,7 +915,8 @@ async def send_message_to_model_wrapper(
784
915
  max_prompt_size=max_tokens,
785
916
  tokenizer_name=tokenizer,
786
917
  vision_enabled=vision_available,
787
- uploaded_image_url=uploaded_image_url,
918
+ query_images=query_images,
919
+ model_type=conversation_config.model_type,
788
920
  )
789
921
 
790
922
  return gemini_send_message_to_model(
@@ -875,6 +1007,7 @@ def send_message_to_model_wrapper_sync(
875
1007
  model_name=chat_model,
876
1008
  max_prompt_size=max_tokens,
877
1009
  vision_enabled=vision_available,
1010
+ model_type=conversation_config.model_type,
878
1011
  )
879
1012
 
880
1013
  return gemini_send_message_to_model(
@@ -900,7 +1033,7 @@ def generate_chat_response(
900
1033
  conversation_id: str = None,
901
1034
  location_data: LocationData = None,
902
1035
  user_name: Optional[str] = None,
903
- uploaded_image_url: Optional[str] = None,
1036
+ query_images: Optional[List[str]] = None,
904
1037
  ) -> Tuple[Union[ThreadedGenerator, Iterator[str]], Dict[str, str]]:
905
1038
  # Initialize Variables
906
1039
  chat_response = None
@@ -919,12 +1052,12 @@ def generate_chat_response(
919
1052
  inferred_queries=inferred_queries,
920
1053
  client_application=client_application,
921
1054
  conversation_id=conversation_id,
922
- uploaded_image_url=uploaded_image_url,
1055
+ query_images=query_images,
923
1056
  )
924
1057
 
925
1058
  conversation_config = ConversationAdapters.get_valid_conversation_config(user, conversation)
926
1059
  vision_available = conversation_config.vision_enabled
927
- if not vision_available and uploaded_image_url:
1060
+ if not vision_available and query_images:
928
1061
  vision_enabled_config = ConversationAdapters.get_vision_enabled_config()
929
1062
  if vision_enabled_config:
930
1063
  conversation_config = vision_enabled_config
@@ -955,7 +1088,7 @@ def generate_chat_response(
955
1088
  chat_response = converse(
956
1089
  compiled_references,
957
1090
  q,
958
- image_url=uploaded_image_url,
1091
+ query_images=query_images,
959
1092
  online_results=online_results,
960
1093
  conversation_log=meta_log,
961
1094
  model=chat_model,
@@ -993,8 +1126,9 @@ def generate_chat_response(
993
1126
  chat_response = converse_gemini(
994
1127
  compiled_references,
995
1128
  q,
996
- online_results,
997
- meta_log,
1129
+ query_images=query_images,
1130
+ online_results=online_results,
1131
+ conversation_log=meta_log,
998
1132
  model=conversation_config.chat_model,
999
1133
  api_key=api_key,
1000
1134
  completion_func=partial_completion,
@@ -1004,6 +1138,7 @@ def generate_chat_response(
1004
1138
  location_data=location_data,
1005
1139
  user_name=user_name,
1006
1140
  agent=agent,
1141
+ vision_available=vision_available,
1007
1142
  )
1008
1143
 
1009
1144
  metadata.update({"chat_model": conversation_config.chat_model})
@@ -1015,6 +1150,22 @@ def generate_chat_response(
1015
1150
  return chat_response, metadata
1016
1151
 
1017
1152
 
1153
+ class ChatRequestBody(BaseModel):
1154
+ q: str
1155
+ n: Optional[int] = 7
1156
+ d: Optional[float] = None
1157
+ stream: Optional[bool] = False
1158
+ title: Optional[str] = None
1159
+ conversation_id: Optional[str] = None
1160
+ city: Optional[str] = None
1161
+ region: Optional[str] = None
1162
+ country: Optional[str] = None
1163
+ country_code: Optional[str] = None
1164
+ timezone: Optional[str] = None
1165
+ images: Optional[list[str]] = None
1166
+ create_new: Optional[bool] = False
1167
+
1168
+
1018
1169
  class ApiUserRateLimiter:
1019
1170
  def __init__(self, requests: int, subscribed_requests: int, window: int, slug: str):
1020
1171
  self.requests = requests
@@ -1060,13 +1211,58 @@ class ApiUserRateLimiter:
1060
1211
  )
1061
1212
  raise HTTPException(
1062
1213
  status_code=429,
1063
- detail="We're glad you're enjoying Khoj! You've exceeded your usage limit for today. Come back tomorrow or subscribe to increase your usage limit via [your settings](https://app.khoj.dev/settings).",
1214
+ detail="I'm glad you're enjoying interacting with me! But you've exceeded your usage limit for today. Come back tomorrow or subscribe to increase your usage limit via [your settings](https://app.khoj.dev/settings).",
1064
1215
  )
1065
1216
 
1066
1217
  # Add the current request to the cache
1067
1218
  UserRequests.objects.create(user=user, slug=self.slug)
1068
1219
 
1069
1220
 
1221
+ class ApiImageRateLimiter:
1222
+ def __init__(self, max_images: int = 10, max_combined_size_mb: float = 10):
1223
+ self.max_images = max_images
1224
+ self.max_combined_size_mb = max_combined_size_mb
1225
+
1226
+ def __call__(self, request: Request, body: ChatRequestBody):
1227
+ if state.billing_enabled is False:
1228
+ return
1229
+
1230
+ # Rate limiting is disabled if user unauthenticated.
1231
+ # Other systems handle authentication
1232
+ if not request.user.is_authenticated:
1233
+ return
1234
+
1235
+ if not body.images:
1236
+ return
1237
+
1238
+ # Check number of images
1239
+ if len(body.images) > self.max_images:
1240
+ raise HTTPException(
1241
+ status_code=429,
1242
+ detail=f"Those are way too many images for me! I can handle up to {self.max_images} images per message.",
1243
+ )
1244
+
1245
+ # Check total size of images
1246
+ total_size_mb = 0.0
1247
+ for image in body.images:
1248
+ # Unquote the image in case it's URL encoded
1249
+ image = unquote(image)
1250
+ # Assuming the image is a base64 encoded string
1251
+ # Remove the data:image/jpeg;base64, part if present
1252
+ if "," in image:
1253
+ image = image.split(",", 1)[1]
1254
+
1255
+ # Decode base64 to get the actual size
1256
+ image_bytes = base64.b64decode(image)
1257
+ total_size_mb += len(image_bytes) / (1024 * 1024) # Convert bytes to MB
1258
+
1259
+ if total_size_mb > self.max_combined_size_mb:
1260
+ raise HTTPException(
1261
+ status_code=429,
1262
+ detail=f"Those images are way too large for me! I can handle up to {self.max_combined_size_mb}MB of images per message.",
1263
+ )
1264
+
1265
+
1070
1266
  class ConversationCommandRateLimiter:
1071
1267
  def __init__(self, trial_rate_limit: int, subscribed_rate_limit: int, slug: str):
1072
1268
  self.slug = slug
@@ -51,17 +51,6 @@ def chat_page(request: Request):
51
51
  )
52
52
 
53
53
 
54
- @web_client.get("/experimental", response_class=FileResponse)
55
- @requires(["authenticated"], redirect="login_page")
56
- def experimental_page(request: Request):
57
- return templates.TemplateResponse(
58
- "index.html",
59
- context={
60
- "request": request,
61
- },
62
- )
63
-
64
-
65
54
  @web_client.get("/factchecker", response_class=FileResponse)
66
55
  def fact_checker_page(request: Request):
67
56
  return templates.TemplateResponse(
khoj/utils/helpers.py CHANGED
@@ -318,6 +318,7 @@ class ConversationCommand(str, Enum):
318
318
  Automation = "automation"
319
319
  AutomatedTask = "automated_task"
320
320
  Summarize = "summarize"
321
+ Diagram = "diagram"
321
322
 
322
323
 
323
324
  command_descriptions = {
@@ -326,10 +327,11 @@ command_descriptions = {
326
327
  ConversationCommand.Default: "The default command when no command specified. It intelligently auto-switches between general and notes mode.",
327
328
  ConversationCommand.Online: "Search for information on the internet.",
328
329
  ConversationCommand.Webpage: "Get information from webpage suggested by you.",
329
- ConversationCommand.Image: "Generate images by describing your imagination in words.",
330
+ ConversationCommand.Image: "Generate illustrative, creative images by describing your imagination in words.",
330
331
  ConversationCommand.Automation: "Automatically run your query at a specified time or interval.",
331
332
  ConversationCommand.Help: "Get help with how to use or setup Khoj from the documentation",
332
333
  ConversationCommand.Summarize: "Get help with a question pertaining to an entire document.",
334
+ ConversationCommand.Diagram: "Draw a flowchart, diagram, or any other visual representation best expressed with primitives like lines, rectangles, and text.",
333
335
  }
334
336
 
335
337
  command_descriptions_for_agent = {
@@ -350,15 +352,17 @@ tool_descriptions_for_llm = {
350
352
  }
351
353
 
352
354
  mode_descriptions_for_llm = {
353
- ConversationCommand.Image: "Use this if the user is requesting you to generate a picture based on their description.",
355
+ ConversationCommand.Image: "Use this if the user is requesting you to create a new picture based on their description.",
354
356
  ConversationCommand.Automation: "Use this if you are confident the user is requesting a response at a scheduled date, time and frequency",
355
- ConversationCommand.Text: "Use this if the other response modes don't seem to fit the query.",
357
+ ConversationCommand.Text: "Use this if a normal text response would be sufficient for accurately responding to the query.",
358
+ ConversationCommand.Diagram: "Use this if the user is requesting a visual representation that requires primitives like lines, rectangles, and text.",
356
359
  }
357
360
 
358
361
  mode_descriptions_for_agent = {
359
362
  ConversationCommand.Image: "Agent can generate image in response.",
360
363
  ConversationCommand.Automation: "Agent can schedule a task to run at a scheduled date, time and frequency in response.",
361
364
  ConversationCommand.Text: "Agent can generate text in response.",
365
+ ConversationCommand.Diagram: "Agent can generate a visual representation that requires primitives like lines, rectangles, and text.",
362
366
  }
363
367
 
364
368
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: khoj
3
- Version: 1.26.4.dev2
3
+ Version: 1.26.5.dev29
4
4
  Summary: Your Second Brain
5
5
  Project-URL: Homepage, https://khoj.dev
6
6
  Project-URL: Documentation, https://docs.khoj.dev