khoj 1.22.3.dev5__py3-none-any.whl → 1.23.3.dev1__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 (66) hide show
  1. khoj/database/adapters/__init__.py +8 -3
  2. khoj/database/migrations/0061_alter_chatmodeloptions_model_type.py +26 -0
  3. khoj/database/migrations/0061_alter_texttoimagemodelconfig_model_type.py +21 -0
  4. khoj/database/migrations/0062_merge_20240913_0222.py +14 -0
  5. khoj/database/models/__init__.py +2 -0
  6. khoj/interface/compiled/404/index.html +1 -1
  7. khoj/interface/compiled/_next/static/chunks/app/agents/{page-3c01900e7b5c7e50.js → page-1ac024e05374f91f.js} +1 -1
  8. khoj/interface/compiled/_next/static/chunks/app/automations/{page-6ea3381528603372.js → page-85e9176b460c5e33.js} +1 -1
  9. khoj/interface/compiled/_next/static/chunks/app/chat/page-ababf339318a3b50.js +1 -0
  10. khoj/interface/compiled/_next/static/chunks/app/factchecker/{page-04a19ab1a988976f.js → page-21cf46aca7e6d487.js} +1 -1
  11. khoj/interface/compiled/_next/static/chunks/app/{page-95ecd0acac7ece82.js → page-b406302925829b15.js} +1 -1
  12. khoj/interface/compiled/_next/static/chunks/app/search/{page-fa15807b1ad7e30b.js → page-fde8c956cc33a187.js} +1 -1
  13. khoj/interface/compiled/_next/static/chunks/app/settings/{page-1a2acc46cdabaf4a.js → page-88737126debb4712.js} +1 -1
  14. khoj/interface/compiled/_next/static/chunks/app/share/chat/{page-e20f54450d3ce6c0.js → page-f11b4fb0f2bc3381.js} +1 -1
  15. khoj/interface/compiled/_next/static/chunks/{webpack-07fad5db87344b82.js → webpack-f162a207b26413cd.js} +1 -1
  16. khoj/interface/compiled/_next/static/css/17e284bae7dc4881.css +1 -0
  17. khoj/interface/compiled/_next/static/css/37a313cb39403a84.css +1 -0
  18. khoj/interface/compiled/_next/static/css/4cae6c0e5c72fb2d.css +1 -0
  19. khoj/interface/compiled/_next/static/css/6bde1f2045622ef7.css +1 -0
  20. khoj/interface/compiled/agents/index.html +1 -1
  21. khoj/interface/compiled/agents/index.txt +2 -2
  22. khoj/interface/compiled/automations/index.html +1 -1
  23. khoj/interface/compiled/automations/index.txt +2 -2
  24. khoj/interface/compiled/chat/index.html +1 -1
  25. khoj/interface/compiled/chat/index.txt +2 -2
  26. khoj/interface/compiled/factchecker/index.html +1 -1
  27. khoj/interface/compiled/factchecker/index.txt +2 -2
  28. khoj/interface/compiled/index.html +1 -1
  29. khoj/interface/compiled/index.txt +2 -2
  30. khoj/interface/compiled/search/index.html +1 -1
  31. khoj/interface/compiled/search/index.txt +2 -2
  32. khoj/interface/compiled/settings/index.html +1 -1
  33. khoj/interface/compiled/settings/index.txt +2 -2
  34. khoj/interface/compiled/share/chat/index.html +1 -1
  35. khoj/interface/compiled/share/chat/index.txt +2 -2
  36. khoj/interface/email/magic_link.html +1 -1
  37. khoj/interface/email/task.html +1 -1
  38. khoj/interface/email/welcome.html +1 -1
  39. khoj/processor/conversation/google/__init__.py +0 -0
  40. khoj/processor/conversation/google/gemini_chat.py +221 -0
  41. khoj/processor/conversation/google/utils.py +192 -0
  42. khoj/processor/conversation/openai/gpt.py +2 -0
  43. khoj/processor/conversation/openai/utils.py +35 -10
  44. khoj/processor/conversation/prompts.py +6 -6
  45. khoj/processor/conversation/utils.py +16 -5
  46. khoj/processor/image/generate.py +212 -0
  47. khoj/processor/tools/online_search.py +2 -1
  48. khoj/routers/api.py +13 -0
  49. khoj/routers/api_chat.py +1 -1
  50. khoj/routers/email.py +6 -1
  51. khoj/routers/helpers.py +86 -164
  52. {khoj-1.22.3.dev5.dist-info → khoj-1.23.3.dev1.dist-info}/METADATA +2 -1
  53. {khoj-1.22.3.dev5.dist-info → khoj-1.23.3.dev1.dist-info}/RECORD +61 -54
  54. khoj/interface/compiled/_next/static/chunks/app/chat/page-132e5199f954559f.js +0 -1
  55. khoj/interface/compiled/_next/static/css/149c5104fe3d38b8.css +0 -1
  56. khoj/interface/compiled/_next/static/css/2272c73fc7a3b571.css +0 -1
  57. khoj/interface/compiled/_next/static/css/553f9cdcc7a2bcd6.css +0 -1
  58. khoj/interface/compiled/_next/static/css/a3530ec58b0b660f.css +0 -1
  59. /khoj/interface/compiled/_next/static/{vjWGo1xJFCitZUk51rujk → BtK3cBCv0oGm04ZdaAvMB}/_buildManifest.js +0 -0
  60. /khoj/interface/compiled/_next/static/{vjWGo1xJFCitZUk51rujk → BtK3cBCv0oGm04ZdaAvMB}/_ssgManifest.js +0 -0
  61. /khoj/interface/compiled/_next/static/chunks/{8423-ce22327cf2d2edae.js → 8423-14fc72aec9104ce9.js} +0 -0
  62. /khoj/interface/compiled/_next/static/chunks/{9178-3a0baad1c172d515.js → 9178-c153fc402c970365.js} +0 -0
  63. /khoj/interface/compiled/_next/static/chunks/{9417-2e54c6fd056982d8.js → 9417-5d14ac74aaab2c66.js} +0 -0
  64. {khoj-1.22.3.dev5.dist-info → khoj-1.23.3.dev1.dist-info}/WHEEL +0 -0
  65. {khoj-1.22.3.dev5.dist-info → khoj-1.23.3.dev1.dist-info}/entry_points.txt +0 -0
  66. {khoj-1.22.3.dev5.dist-info → khoj-1.23.3.dev1.dist-info}/licenses/LICENSE +0 -0
khoj/routers/helpers.py CHANGED
@@ -1,7 +1,5 @@
1
1
  import asyncio
2
- import base64
3
2
  import hashlib
4
- import io
5
3
  import json
6
4
  import logging
7
5
  import math
@@ -16,7 +14,6 @@ from typing import (
16
14
  Annotated,
17
15
  Any,
18
16
  AsyncGenerator,
19
- Callable,
20
17
  Dict,
21
18
  Iterator,
22
19
  List,
@@ -24,17 +21,15 @@ from typing import (
24
21
  Tuple,
25
22
  Union,
26
23
  )
27
- from urllib.parse import parse_qs, urlencode, urljoin, urlparse
24
+ from urllib.parse import parse_qs, urljoin, urlparse
28
25
 
29
26
  import cron_descriptor
30
- import openai
31
27
  import pytz
32
28
  import requests
33
29
  from apscheduler.job import Job
34
30
  from apscheduler.triggers.cron import CronTrigger
35
31
  from asgiref.sync import sync_to_async
36
32
  from fastapi import Depends, Header, HTTPException, Request, UploadFile
37
- from PIL import Image
38
33
  from starlette.authentication import has_required_scope
39
34
  from starlette.requests import URL
40
35
 
@@ -76,6 +71,10 @@ from khoj.processor.conversation.anthropic.anthropic_chat import (
76
71
  anthropic_send_message_to_model,
77
72
  converse_anthropic,
78
73
  )
74
+ from khoj.processor.conversation.google.gemini_chat import (
75
+ converse_gemini,
76
+ gemini_send_message_to_model,
77
+ )
79
78
  from khoj.processor.conversation.offline.chat_model import (
80
79
  converse_offline,
81
80
  send_message_to_model_offline,
@@ -84,11 +83,11 @@ from khoj.processor.conversation.openai.gpt import converse, send_message_to_mod
84
83
  from khoj.processor.conversation.utils import (
85
84
  ThreadedGenerator,
86
85
  generate_chatml_messages_with_context,
86
+ remove_json_codeblock,
87
87
  save_to_conversation_log,
88
88
  )
89
89
  from khoj.processor.speech.text_to_speech import is_eleven_labs_enabled
90
90
  from khoj.routers.email import is_resend_enabled, send_task_email
91
- from khoj.routers.storage import upload_image
92
91
  from khoj.routers.twilio import is_twilio_enabled
93
92
  from khoj.search_type import text_search
94
93
  from khoj.utils import state
@@ -96,8 +95,6 @@ from khoj.utils.config import OfflineChatProcessorModel
96
95
  from khoj.utils.helpers import (
97
96
  LRU,
98
97
  ConversationCommand,
99
- ImageIntentType,
100
- convert_image_to_webp,
101
98
  is_none_or_empty,
102
99
  is_valid_url,
103
100
  log_telemetry,
@@ -136,7 +133,7 @@ async def is_ready_to_chat(user: KhojUser):
136
133
  await ConversationAdapters.aget_default_conversation_config()
137
134
  )
138
135
 
139
- if user_conversation_config and user_conversation_config.model_type == "offline":
136
+ if user_conversation_config and user_conversation_config.model_type == ChatModelOptions.ModelType.OFFLINE:
140
137
  chat_model = user_conversation_config.chat_model
141
138
  max_tokens = user_conversation_config.max_prompt_size
142
139
  if state.offline_chat_processor_config is None:
@@ -146,7 +143,14 @@ async def is_ready_to_chat(user: KhojUser):
146
143
 
147
144
  if (
148
145
  user_conversation_config
149
- and (user_conversation_config.model_type == "openai" or user_conversation_config.model_type == "anthropic")
146
+ and (
147
+ user_conversation_config.model_type
148
+ in [
149
+ ChatModelOptions.ModelType.OPENAI,
150
+ ChatModelOptions.ModelType.ANTHROPIC,
151
+ ChatModelOptions.ModelType.GOOGLE,
152
+ ]
153
+ )
150
154
  and user_conversation_config.openai_config
151
155
  ):
152
156
  return True
@@ -287,9 +291,7 @@ async def aget_relevant_information_sources(
287
291
 
288
292
  try:
289
293
  response = response.strip()
290
- # Remove any markdown json codeblock formatting if present (useful for gemma-2)
291
- if response.startswith("```json"):
292
- response = response[7:-3]
294
+ response = remove_json_codeblock(response)
293
295
  response = json.loads(response)
294
296
  response = [q.strip() for q in response["source"] if q.strip()]
295
297
  if not isinstance(response, list) or not response or len(response) == 0:
@@ -342,7 +344,9 @@ async def aget_relevant_output_modes(
342
344
  response = await send_message_to_model_wrapper(relevant_mode_prompt, response_type="json_object")
343
345
 
344
346
  try:
345
- response = json.loads(response.strip())
347
+ response = response.strip()
348
+ response = remove_json_codeblock(response)
349
+ response = json.loads(response)
346
350
 
347
351
  if is_none_or_empty(response):
348
352
  return ConversationCommand.Text
@@ -422,9 +426,7 @@ async def generate_online_subqueries(
422
426
  # Validate that the response is a non-empty, JSON-serializable list
423
427
  try:
424
428
  response = response.strip()
425
- # Remove any markdown json codeblock formatting if present (useful for gemma-2)
426
- if response.startswith("```json") and response.endswith("```"):
427
- response = response[7:-3]
429
+ response = remove_json_codeblock(response)
428
430
  response = json.loads(response)
429
431
  response = [q.strip() for q in response["queries"] if q.strip()]
430
432
  if not isinstance(response, list) or not response or len(response) == 0:
@@ -558,7 +560,7 @@ async def generate_better_image_prompt(
558
560
  references=user_references,
559
561
  online_results=simplified_online_results,
560
562
  )
561
- elif model_type == TextToImageModelConfig.ModelType.STABILITYAI:
563
+ elif model_type in [TextToImageModelConfig.ModelType.STABILITYAI, TextToImageModelConfig.ModelType.REPLICATE]:
562
564
  image_prompt = prompts.image_generation_improve_prompt_sd.format(
563
565
  query=q,
564
566
  chat_history=conversation_history,
@@ -607,9 +609,10 @@ async def send_message_to_model_wrapper(
607
609
  else conversation_config.max_prompt_size
608
610
  )
609
611
  tokenizer = conversation_config.tokenizer
612
+ model_type = conversation_config.model_type
610
613
  vision_available = conversation_config.vision_enabled
611
614
 
612
- if conversation_config.model_type == "offline":
615
+ if model_type == ChatModelOptions.ModelType.OFFLINE:
613
616
  if state.offline_chat_processor_config is None or state.offline_chat_processor_config.loaded_model is None:
614
617
  state.offline_chat_processor_config = OfflineChatProcessorModel(chat_model, max_tokens)
615
618
 
@@ -633,7 +636,7 @@ async def send_message_to_model_wrapper(
633
636
  response_type=response_type,
634
637
  )
635
638
 
636
- elif conversation_config.model_type == "openai":
639
+ elif model_type == ChatModelOptions.ModelType.OPENAI:
637
640
  openai_chat_config = conversation_config.openai_config
638
641
  api_key = openai_chat_config.api_key
639
642
  api_base_url = openai_chat_config.api_base_url
@@ -657,7 +660,7 @@ async def send_message_to_model_wrapper(
657
660
  )
658
661
 
659
662
  return openai_response
660
- elif conversation_config.model_type == "anthropic":
663
+ elif model_type == ChatModelOptions.ModelType.ANTHROPIC:
661
664
  api_key = conversation_config.openai_config.api_key
662
665
  truncated_messages = generate_chatml_messages_with_context(
663
666
  user_message=message,
@@ -666,6 +669,7 @@ async def send_message_to_model_wrapper(
666
669
  max_prompt_size=max_tokens,
667
670
  tokenizer_name=tokenizer,
668
671
  vision_enabled=vision_available,
672
+ uploaded_image_url=uploaded_image_url,
669
673
  model_type=conversation_config.model_type,
670
674
  )
671
675
 
@@ -674,6 +678,21 @@ async def send_message_to_model_wrapper(
674
678
  api_key=api_key,
675
679
  model=chat_model,
676
680
  )
681
+ elif model_type == ChatModelOptions.ModelType.GOOGLE:
682
+ api_key = conversation_config.openai_config.api_key
683
+ truncated_messages = generate_chatml_messages_with_context(
684
+ user_message=message,
685
+ system_message=system_message,
686
+ model_name=chat_model,
687
+ max_prompt_size=max_tokens,
688
+ tokenizer_name=tokenizer,
689
+ vision_enabled=vision_available,
690
+ uploaded_image_url=uploaded_image_url,
691
+ )
692
+
693
+ return gemini_send_message_to_model(
694
+ messages=truncated_messages, api_key=api_key, model=chat_model, response_type=response_type
695
+ )
677
696
  else:
678
697
  raise HTTPException(status_code=500, detail="Invalid conversation config")
679
698
 
@@ -692,7 +711,7 @@ def send_message_to_model_wrapper_sync(
692
711
  max_tokens = conversation_config.max_prompt_size
693
712
  vision_available = conversation_config.vision_enabled
694
713
 
695
- if conversation_config.model_type == "offline":
714
+ if conversation_config.model_type == ChatModelOptions.ModelType.OFFLINE:
696
715
  if state.offline_chat_processor_config is None or state.offline_chat_processor_config.loaded_model is None:
697
716
  state.offline_chat_processor_config = OfflineChatProcessorModel(chat_model, max_tokens)
698
717
 
@@ -714,7 +733,7 @@ def send_message_to_model_wrapper_sync(
714
733
  response_type=response_type,
715
734
  )
716
735
 
717
- elif conversation_config.model_type == "openai":
736
+ elif conversation_config.model_type == ChatModelOptions.ModelType.OPENAI:
718
737
  api_key = conversation_config.openai_config.api_key
719
738
  truncated_messages = generate_chatml_messages_with_context(
720
739
  user_message=message,
@@ -730,7 +749,7 @@ def send_message_to_model_wrapper_sync(
730
749
 
731
750
  return openai_response
732
751
 
733
- elif conversation_config.model_type == "anthropic":
752
+ elif conversation_config.model_type == ChatModelOptions.ModelType.ANTHROPIC:
734
753
  api_key = conversation_config.openai_config.api_key
735
754
  truncated_messages = generate_chatml_messages_with_context(
736
755
  user_message=message,
@@ -746,6 +765,22 @@ def send_message_to_model_wrapper_sync(
746
765
  api_key=api_key,
747
766
  model=chat_model,
748
767
  )
768
+
769
+ elif conversation_config.model_type == ChatModelOptions.ModelType.GOOGLE:
770
+ api_key = conversation_config.openai_config.api_key
771
+ truncated_messages = generate_chatml_messages_with_context(
772
+ user_message=message,
773
+ system_message=system_message,
774
+ model_name=chat_model,
775
+ max_prompt_size=max_tokens,
776
+ vision_enabled=vision_available,
777
+ )
778
+
779
+ return gemini_send_message_to_model(
780
+ messages=truncated_messages,
781
+ api_key=api_key,
782
+ model=chat_model,
783
+ )
749
784
  else:
750
785
  raise HTTPException(status_code=500, detail="Invalid conversation config")
751
786
 
@@ -811,7 +846,7 @@ def generate_chat_response(
811
846
  agent=agent,
812
847
  )
813
848
 
814
- elif conversation_config.model_type == "openai":
849
+ elif conversation_config.model_type == ChatModelOptions.ModelType.OPENAI:
815
850
  openai_chat_config = conversation_config.openai_config
816
851
  api_key = openai_chat_config.api_key
817
852
  chat_model = conversation_config.chat_model
@@ -834,7 +869,7 @@ def generate_chat_response(
834
869
  vision_available=vision_available,
835
870
  )
836
871
 
837
- elif conversation_config.model_type == "anthropic":
872
+ elif conversation_config.model_type == ChatModelOptions.ModelType.ANTHROPIC:
838
873
  api_key = conversation_config.openai_config.api_key
839
874
  chat_response = converse_anthropic(
840
875
  compiled_references,
@@ -851,6 +886,23 @@ def generate_chat_response(
851
886
  user_name=user_name,
852
887
  agent=agent,
853
888
  )
889
+ elif conversation_config.model_type == ChatModelOptions.ModelType.GOOGLE:
890
+ api_key = conversation_config.openai_config.api_key
891
+ chat_response = converse_gemini(
892
+ compiled_references,
893
+ q,
894
+ online_results,
895
+ meta_log,
896
+ model=conversation_config.chat_model,
897
+ api_key=api_key,
898
+ completion_func=partial_completion,
899
+ conversation_commands=conversation_commands,
900
+ max_prompt_size=conversation_config.max_prompt_size,
901
+ tokenizer_name=conversation_config.tokenizer,
902
+ location_data=location_data,
903
+ user_name=user_name,
904
+ agent=agent,
905
+ )
854
906
 
855
907
  metadata.update({"chat_model": conversation_config.chat_model})
856
908
 
@@ -861,129 +913,6 @@ def generate_chat_response(
861
913
  return chat_response, metadata
862
914
 
863
915
 
864
- async def text_to_image(
865
- message: str,
866
- user: KhojUser,
867
- conversation_log: dict,
868
- location_data: LocationData,
869
- references: List[Dict[str, Any]],
870
- online_results: Dict[str, Any],
871
- subscribed: bool = False,
872
- send_status_func: Optional[Callable] = None,
873
- uploaded_image_url: Optional[str] = None,
874
- ):
875
- status_code = 200
876
- image = None
877
- response = None
878
- image_url = None
879
- intent_type = ImageIntentType.TEXT_TO_IMAGE_V3
880
-
881
- text_to_image_config = await ConversationAdapters.aget_user_text_to_image_model(user)
882
- if not text_to_image_config:
883
- # If the user has not configured a text to image model, return an unsupported on server error
884
- status_code = 501
885
- message = "Failed to generate image. Setup image generation on the server."
886
- yield image_url or image, status_code, message, intent_type.value
887
- return
888
-
889
- text2image_model = text_to_image_config.model_name
890
- chat_history = ""
891
- for chat in conversation_log.get("chat", [])[-4:]:
892
- if chat["by"] == "khoj" and chat["intent"].get("type") in ["remember", "reminder"]:
893
- chat_history += f"Q: {chat['intent']['query']}\n"
894
- chat_history += f"A: {chat['message']}\n"
895
- elif chat["by"] == "khoj" and "text-to-image" in chat["intent"].get("type"):
896
- chat_history += f"Q: Prompt: {chat['intent']['query']}\n"
897
- chat_history += f"A: Improved Prompt: {chat['intent']['inferred-queries'][0]}\n"
898
-
899
- if send_status_func:
900
- async for event in send_status_func("**Enhancing the Painting Prompt**"):
901
- yield {ChatEvent.STATUS: event}
902
- improved_image_prompt = await generate_better_image_prompt(
903
- message,
904
- chat_history,
905
- location_data=location_data,
906
- note_references=references,
907
- online_results=online_results,
908
- model_type=text_to_image_config.model_type,
909
- subscribed=subscribed,
910
- uploaded_image_url=uploaded_image_url,
911
- )
912
-
913
- if send_status_func:
914
- async for event in send_status_func(f"**Painting to Imagine**:\n{improved_image_prompt}"):
915
- yield {ChatEvent.STATUS: event}
916
-
917
- if text_to_image_config.model_type == TextToImageModelConfig.ModelType.OPENAI:
918
- with timer("Generate image with OpenAI", logger):
919
- if text_to_image_config.api_key:
920
- api_key = text_to_image_config.api_key
921
- elif text_to_image_config.openai_config:
922
- api_key = text_to_image_config.openai_config.api_key
923
- elif state.openai_client:
924
- api_key = state.openai_client.api_key
925
- auth_header = {"Authorization": f"Bearer {api_key}"} if api_key else {}
926
- try:
927
- response = state.openai_client.images.generate(
928
- prompt=improved_image_prompt,
929
- model=text2image_model,
930
- response_format="b64_json",
931
- extra_headers=auth_header,
932
- )
933
- image = response.data[0].b64_json
934
- decoded_image = base64.b64decode(image)
935
- except openai.OpenAIError or openai.BadRequestError or openai.APIConnectionError as e:
936
- if "content_policy_violation" in e.message:
937
- logger.error(f"Image Generation blocked by OpenAI: {e}")
938
- status_code = e.status_code # type: ignore
939
- message = f"Image generation blocked by OpenAI: {e.message}" # type: ignore
940
- yield image_url or image, status_code, message, intent_type.value
941
- return
942
- else:
943
- logger.error(f"Image Generation failed with {e}", exc_info=True)
944
- message = f"Image generation failed with OpenAI error: {e.message}" # type: ignore
945
- status_code = e.status_code # type: ignore
946
- yield image_url or image, status_code, message, intent_type.value
947
- return
948
-
949
- elif text_to_image_config.model_type == TextToImageModelConfig.ModelType.STABILITYAI:
950
- with timer("Generate image with Stability AI", logger):
951
- try:
952
- response = requests.post(
953
- f"https://api.stability.ai/v2beta/stable-image/generate/sd3",
954
- headers={"authorization": f"Bearer {text_to_image_config.api_key}", "accept": "image/*"},
955
- files={"none": ""},
956
- data={
957
- "prompt": improved_image_prompt,
958
- "model": text2image_model,
959
- "mode": "text-to-image",
960
- "output_format": "png",
961
- "aspect_ratio": "1:1",
962
- },
963
- )
964
- decoded_image = response.content
965
- except requests.RequestException as e:
966
- logger.error(f"Image Generation failed with {e}", exc_info=True)
967
- message = f"Image generation failed with Stability AI error: {e}"
968
- status_code = e.status_code # type: ignore
969
- yield image_url or image, status_code, message, intent_type.value
970
- return
971
-
972
- with timer("Convert image to webp", logger):
973
- # Convert png to webp for faster loading
974
- webp_image_bytes = convert_image_to_webp(decoded_image)
975
-
976
- with timer("Upload image to S3", logger):
977
- image_url = upload_image(webp_image_bytes, user.uuid)
978
- if image_url:
979
- intent_type = ImageIntentType.TEXT_TO_IMAGE2
980
- else:
981
- intent_type = ImageIntentType.TEXT_TO_IMAGE_V3
982
- image = base64.b64encode(webp_image_bytes).decode("utf-8")
983
-
984
- yield image_url or image, status_code, improved_image_prompt, intent_type.value
985
-
986
-
987
916
  class ApiUserRateLimiter:
988
917
  def __init__(self, requests: int, subscribed_requests: int, window: int, slug: str):
989
918
  self.requests = requests
@@ -1217,21 +1146,14 @@ def scheduled_chat(
1217
1146
  token = token[0].token
1218
1147
  headers["Authorization"] = f"Bearer {token}"
1219
1148
 
1220
- # Log request details
1221
- logger.info(f"POST URL: {url}")
1222
- logger.info(f"Headers: {headers}")
1223
- logger.info(f"Payload: {json_payload}")
1224
-
1225
1149
  # Call the chat API endpoint with authenticated user token and query
1226
- raw_response = requests.post(url, headers=headers, json=json_payload)
1227
-
1228
- # Log response details
1229
- logger.info(f"Response status code: {raw_response.status_code}")
1230
- logger.info(f"Response headers: {raw_response.headers}")
1231
- logger.info(f"Response text: {raw_response.text}")
1232
- if raw_response.history:
1233
- for resp in raw_response.history:
1234
- logger.info(f"Redirected from {resp.url} with status code {resp.status_code}")
1150
+ raw_response = requests.post(url, headers=headers, json=json_payload, allow_redirects=False)
1151
+
1152
+ # Handle redirect manually if necessary
1153
+ if raw_response.status_code in [301, 302]:
1154
+ redirect_url = raw_response.headers["Location"]
1155
+ logger.info(f"Redirecting to {redirect_url}")
1156
+ raw_response = requests.post(redirect_url, headers=headers, json=json_payload)
1235
1157
 
1236
1158
  # Stop if the chat API call was not successful
1237
1159
  if raw_response.status_code != 200:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: khoj
3
- Version: 1.22.3.dev5
3
+ Version: 1.23.3.dev1
4
4
  Summary: Your Second Brain
5
5
  Project-URL: Homepage, https://khoj.dev
6
6
  Project-URL: Documentation, https://docs.khoj.dev
@@ -36,6 +36,7 @@ Requires-Dist: django==5.0.8
36
36
  Requires-Dist: docx2txt==0.8
37
37
  Requires-Dist: einops==0.8.0
38
38
  Requires-Dist: fastapi>=0.110.0
39
+ Requires-Dist: google-generativeai==0.7.2
39
40
  Requires-Dist: httpx==0.25.0
40
41
  Requires-Dist: huggingface-hub>=0.22.2
41
42
  Requires-Dist: itsdangerous==2.1.2