khoj 1.26.4.dev2__py3-none-any.whl → 1.26.5.dev16__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/interface/compiled/404/index.html +1 -1
- khoj/interface/compiled/_next/static/chunks/1210.132a7e1910006bbb.js +1 -0
- khoj/interface/compiled/_next/static/chunks/1459.690bf20e7d7b7090.js +1 -0
- khoj/interface/compiled/_next/static/chunks/1603-bfc0b26e32ad88e3.js +1 -0
- khoj/interface/compiled/_next/static/chunks/2697-37579bcc7593dd5c.js +1 -0
- khoj/interface/compiled/_next/static/chunks/3423-aad88d6c1f029135.js +1 -0
- khoj/interface/compiled/_next/static/chunks/6327-18f0b45cc5a13afb.js +3 -0
- khoj/interface/compiled/_next/static/chunks/{4086-2c74808ba38a5a0f.js → 8840-b8d7b9f0923c6651.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/9417-759984ad62caa3dc.js +1 -0
- khoj/interface/compiled/_next/static/chunks/9479-fcce773453e472c4.js +1 -0
- khoj/interface/compiled/_next/static/chunks/94ca1967.5584df65931cfe83.js +1 -0
- khoj/interface/compiled/_next/static/chunks/964ecbae.ea4eab2a3a835ffe.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/agents/{page-88aa3042711107b7.js → page-997bf85681256672.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/automations/{page-5480731341f34450.js → page-1688dead2f21270d.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/chat/page-027e61d082025c50.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/factchecker/{page-e7b34316ec6f44de.js → page-f544113d240423e9.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/{page-10a5aad6e04f3cf8.js → page-88139ac728fe3533.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/search/{page-d56541c746fded7d.js → page-3ada11cda5050eeb.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/settings/{page-e044a999468a7c5d.js → page-fa11cafaec7ab39f.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/share/chat/page-511186e77624eaec.js +1 -0
- khoj/interface/compiled/_next/static/chunks/webpack-c48e5093123a4a56.js +1 -0
- khoj/interface/compiled/_next/static/css/{c808691c459e3887.css → 3cf13271869a4aeb.css} +1 -1
- khoj/interface/compiled/_next/static/css/4cae6c0e5c72fb2d.css +1 -0
- khoj/interface/compiled/_next/static/css/825406e5ebee86d3.css +25 -0
- khoj/interface/compiled/_next/static/css/f84cf008d5ff4161.css +1 -0
- khoj/interface/compiled/agents/index.html +1 -1
- khoj/interface/compiled/agents/index.txt +2 -2
- khoj/interface/compiled/automations/index.html +1 -1
- khoj/interface/compiled/automations/index.txt +2 -2
- khoj/interface/compiled/chat/index.html +1 -1
- khoj/interface/compiled/chat/index.txt +2 -2
- khoj/interface/compiled/factchecker/index.html +1 -1
- khoj/interface/compiled/factchecker/index.txt +2 -2
- khoj/interface/compiled/index.html +1 -1
- khoj/interface/compiled/index.txt +2 -2
- khoj/interface/compiled/search/index.html +1 -1
- khoj/interface/compiled/search/index.txt +2 -2
- khoj/interface/compiled/settings/index.html +1 -1
- khoj/interface/compiled/settings/index.txt +2 -2
- khoj/interface/compiled/share/chat/index.html +1 -1
- khoj/interface/compiled/share/chat/index.txt +2 -2
- khoj/processor/conversation/google/gemini_chat.py +28 -13
- khoj/processor/conversation/google/utils.py +34 -12
- khoj/processor/conversation/openai/gpt.py +4 -4
- khoj/processor/conversation/prompts.py +144 -0
- khoj/processor/conversation/utils.py +22 -13
- khoj/processor/image/generate.py +5 -5
- khoj/processor/tools/online_search.py +4 -4
- khoj/routers/api.py +4 -2
- khoj/routers/api_chat.py +85 -46
- khoj/routers/helpers.py +225 -29
- khoj/routers/web_client.py +0 -11
- khoj/utils/helpers.py +7 -3
- {khoj-1.26.4.dev2.dist-info → khoj-1.26.5.dev16.dist-info}/METADATA +1 -1
- {khoj-1.26.4.dev2.dist-info → khoj-1.26.5.dev16.dist-info}/RECORD +60 -56
- khoj/interface/compiled/_next/static/chunks/1603-fa3ee48860b9dc5c.js +0 -1
- khoj/interface/compiled/_next/static/chunks/2697-a38d01981ad3bdf8.js +0 -1
- khoj/interface/compiled/_next/static/chunks/4051-2cf66369d6ca0f1d.js +0 -3
- khoj/interface/compiled/_next/static/chunks/9178-899fe9a6b754ecfe.js +0 -1
- khoj/interface/compiled/_next/static/chunks/9417-46ed3aaa639c85ef.js +0 -1
- khoj/interface/compiled/_next/static/chunks/9479-ea776e73f549090c.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/chat/page-702057ccbcf27881.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/share/chat/page-fbbd66a4d4633438.js +0 -1
- khoj/interface/compiled/_next/static/chunks/webpack-2651a68f46ac3cb7.js +0 -1
- khoj/interface/compiled/_next/static/css/2de69f0be774c768.css +0 -1
- khoj/interface/compiled/_next/static/css/3e1f1fdd70775091.css +0 -1
- khoj/interface/compiled/_next/static/css/b9a6bf04305d98d7.css +0 -25
- /khoj/interface/compiled/_next/static/{wyjqS7cuSX-u62BTNYqhU → 1dVlkdPTTw5b6saGVG4b3}/_buildManifest.js +0 -0
- /khoj/interface/compiled/_next/static/{wyjqS7cuSX-u62BTNYqhU → 1dVlkdPTTw5b6saGVG4b3}/_ssgManifest.js +0 -0
- {khoj-1.26.4.dev2.dist-info → khoj-1.26.5.dev16.dist-info}/WHEEL +0 -0
- {khoj-1.26.4.dev2.dist-info → khoj-1.26.5.dev16.dist-info}/entry_points.txt +0 -0
- {khoj-1.26.4.dev2.dist-info → khoj-1.26.5.dev16.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
|
-
|
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
|
313
|
-
query = f"[placeholder for user attached
|
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
|
-
|
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
|
393
|
-
query = f"[placeholder for user attached
|
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
|
-
|
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,
|
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
|
-
|
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,
|
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,
|
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,
|
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
|
-
|
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
|
-
|
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
|
-
|
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,
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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
|
-
|
997
|
-
|
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="
|
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
|
khoj/routers/web_client.py
CHANGED
@@ -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
|
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
|
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
|
|