khoj 1.26.2__py3-none-any.whl → 1.26.5.dev34__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- khoj/configure.py +3 -3
- khoj/database/adapters/__init__.py +40 -8
- khoj/database/migrations/0070_alter_agent_input_tools_alter_agent_output_modes.py +46 -0
- khoj/database/migrations/0071_subscription_enabled_trial_at_and_more.py +32 -0
- khoj/database/models/__init__.py +8 -3
- 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/1279-f37ee4a388ebf544.js +1 -0
- khoj/interface/compiled/_next/static/chunks/1459.690bf20e7d7b7090.js +1 -0
- khoj/interface/compiled/_next/static/chunks/1603-b9d95833e0e025e8.js +1 -0
- khoj/interface/compiled/_next/static/chunks/1970-1d6d0c1b00b4f343.js +1 -0
- khoj/interface/compiled/_next/static/chunks/2697-61fcba89fd87eab4.js +1 -0
- khoj/interface/compiled/_next/static/chunks/3423-8e9c420574a9fbe3.js +1 -0
- khoj/interface/compiled/_next/static/chunks/394-6bcb8c429f168f21.js +3 -0
- khoj/interface/compiled/_next/static/chunks/4602-8eeb4b76385ad159.js +1 -0
- khoj/interface/compiled/_next/static/chunks/5512-94c7c2bbcf58c19d.js +1 -0
- khoj/interface/compiled/_next/static/chunks/7113-f2e114d7034a0835.js +1 -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-4b443fdcc99141c9.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-2beaba7c9bb750bd.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/automations/{page-5480731341f34450.js → page-9b5c77e0b0dd772c.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/chat/page-151232d8417a1ea1.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/factchecker/{page-e7b34316ec6f44de.js → page-798904432c2417c4.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/{page-10a5aad6e04f3cf8.js → page-db4e38a5255af7ad.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/search/{page-d56541c746fded7d.js → page-ab2995529ece3140.js} +1 -1
- khoj/interface/compiled/_next/static/chunks/app/settings/page-3e9cf5ed5ace4310.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/share/chat/page-6a01e07fb244c10c.js +1 -0
- khoj/interface/compiled/_next/static/chunks/webpack-313247d7eb764923.js +1 -0
- khoj/interface/compiled/_next/static/css/{c808691c459e3887.css → 3cf13271869a4aeb.css} +1 -1
- khoj/interface/compiled/_next/static/css/76d55eb435962b19.css +25 -0
- khoj/interface/compiled/_next/static/css/{3e1f1fdd70775091.css → 80bd6301fc657983.css} +1 -1
- khoj/interface/compiled/_next/static/css/{2de69f0be774c768.css → b70402177a7c3207.css} +1 -1
- 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 +13 -4
- khoj/routers/api_agents.py +41 -20
- khoj/routers/api_chat.py +85 -46
- khoj/routers/{subscription.py → api_subscription.py} +21 -3
- khoj/routers/auth.py +2 -2
- khoj/routers/helpers.py +235 -30
- khoj/routers/web_client.py +0 -11
- khoj/utils/helpers.py +7 -3
- {khoj-1.26.2.dist-info → khoj-1.26.5.dev34.dist-info}/METADATA +2 -2
- {khoj-1.26.2.dist-info → khoj-1.26.5.dev34.dist-info}/RECORD +73 -66
- khoj/interface/compiled/_next/static/chunks/121-7024f479c297aef0.js +0 -1
- 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/477-ec86e93db10571c1.js +0 -1
- khoj/interface/compiled/_next/static/chunks/51-e8f5bdb69b5ea421.js +0 -1
- khoj/interface/compiled/_next/static/chunks/7762-79f2205740622b5c.js +0 -1
- 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/agents/page-88aa3042711107b7.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/chat/page-702057ccbcf27881.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/settings/page-e044a999468a7c5d.js +0 -1
- khoj/interface/compiled/_next/static/chunks/app/share/chat/page-fbbd66a4d4633438.js +0 -1
- khoj/interface/compiled/_next/static/chunks/webpack-64dc39af85cd2625.js +0 -1
- khoj/interface/compiled/_next/static/css/b9a6bf04305d98d7.css +0 -25
- /khoj/interface/compiled/_next/static/{eim4XajTfG4ub4ft5AEkJ → 7viHIza-WalEOzloM67l4}/_buildManifest.js +0 -0
- /khoj/interface/compiled/_next/static/{eim4XajTfG4ub4ft5AEkJ → 7viHIza-WalEOzloM67l4}/_ssgManifest.js +0 -0
- {khoj-1.26.2.dist-info → khoj-1.26.5.dev34.dist-info}/WHEEL +0 -0
- {khoj-1.26.2.dist-info → khoj-1.26.5.dev34.dist-info}/entry_points.txt +0 -0
- {khoj-1.26.2.dist-info → khoj-1.26.5.dev34.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,11 +32,13 @@ 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
|
|
36
39
|
from khoj.database import adapters
|
37
40
|
from khoj.database.adapters import (
|
41
|
+
LENGTH_OF_FREE_TRIAL,
|
38
42
|
AgentAdapters,
|
39
43
|
AutomationAdapters,
|
40
44
|
ConversationAdapters,
|
@@ -215,6 +219,9 @@ def construct_chat_history(conversation_history: dict, n: int = 4, agent_name="A
|
|
215
219
|
elif chat["by"] == "khoj" and ("text-to-image" in chat["intent"].get("type")):
|
216
220
|
chat_history += f"User: {chat['intent']['query']}\n"
|
217
221
|
chat_history += f"{agent_name}: [generated image redacted for space]\n"
|
222
|
+
elif chat["by"] == "khoj" and ("excalidraw" in chat["intent"].get("type")):
|
223
|
+
chat_history += f"User: {chat['intent']['query']}\n"
|
224
|
+
chat_history += f"{agent_name}: {chat['intent']['inferred-queries'][0]}\n"
|
218
225
|
return chat_history
|
219
226
|
|
220
227
|
|
@@ -235,6 +242,8 @@ def get_conversation_command(query: str, any_references: bool = False) -> Conver
|
|
235
242
|
return ConversationCommand.AutomatedTask
|
236
243
|
elif query.startswith("/summarize"):
|
237
244
|
return ConversationCommand.Summarize
|
245
|
+
elif query.startswith("/diagram"):
|
246
|
+
return ConversationCommand.Diagram
|
238
247
|
# If no relevant notes found for the given query
|
239
248
|
elif not any_references:
|
240
249
|
return ConversationCommand.General
|
@@ -290,7 +299,7 @@ async def aget_relevant_information_sources(
|
|
290
299
|
conversation_history: dict,
|
291
300
|
is_task: bool,
|
292
301
|
user: KhojUser,
|
293
|
-
|
302
|
+
query_images: List[str] = None,
|
294
303
|
agent: Agent = None,
|
295
304
|
):
|
296
305
|
"""
|
@@ -309,8 +318,8 @@ async def aget_relevant_information_sources(
|
|
309
318
|
|
310
319
|
chat_history = construct_chat_history(conversation_history)
|
311
320
|
|
312
|
-
if
|
313
|
-
query = f"[placeholder for user attached
|
321
|
+
if query_images:
|
322
|
+
query = f"[placeholder for {len(query_images)} user attached images]\n{query}"
|
314
323
|
|
315
324
|
personality_context = (
|
316
325
|
prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else ""
|
@@ -367,7 +376,7 @@ async def aget_relevant_output_modes(
|
|
367
376
|
conversation_history: dict,
|
368
377
|
is_task: bool = False,
|
369
378
|
user: KhojUser = None,
|
370
|
-
|
379
|
+
query_images: List[str] = None,
|
371
380
|
agent: Agent = None,
|
372
381
|
):
|
373
382
|
"""
|
@@ -389,8 +398,8 @@ async def aget_relevant_output_modes(
|
|
389
398
|
|
390
399
|
chat_history = construct_chat_history(conversation_history)
|
391
400
|
|
392
|
-
if
|
393
|
-
query = f"[placeholder for user attached
|
401
|
+
if query_images:
|
402
|
+
query = f"[placeholder for {len(query_images)} user attached images]\n{query}"
|
394
403
|
|
395
404
|
personality_context = (
|
396
405
|
prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else ""
|
@@ -433,7 +442,7 @@ async def infer_webpage_urls(
|
|
433
442
|
conversation_history: dict,
|
434
443
|
location_data: LocationData,
|
435
444
|
user: KhojUser,
|
436
|
-
|
445
|
+
query_images: List[str] = None,
|
437
446
|
agent: Agent = None,
|
438
447
|
) -> List[str]:
|
439
448
|
"""
|
@@ -459,7 +468,7 @@ async def infer_webpage_urls(
|
|
459
468
|
|
460
469
|
with timer("Chat actor: Infer webpage urls to read", logger):
|
461
470
|
response = await send_message_to_model_wrapper(
|
462
|
-
online_queries_prompt,
|
471
|
+
online_queries_prompt, query_images=query_images, response_type="json_object", user=user
|
463
472
|
)
|
464
473
|
|
465
474
|
# Validate that the response is a non-empty, JSON-serializable list of URLs
|
@@ -479,7 +488,7 @@ async def generate_online_subqueries(
|
|
479
488
|
conversation_history: dict,
|
480
489
|
location_data: LocationData,
|
481
490
|
user: KhojUser,
|
482
|
-
|
491
|
+
query_images: List[str] = None,
|
483
492
|
agent: Agent = None,
|
484
493
|
) -> List[str]:
|
485
494
|
"""
|
@@ -505,7 +514,7 @@ async def generate_online_subqueries(
|
|
505
514
|
|
506
515
|
with timer("Chat actor: Generate online search subqueries", logger):
|
507
516
|
response = await send_message_to_model_wrapper(
|
508
|
-
online_queries_prompt,
|
517
|
+
online_queries_prompt, query_images=query_images, response_type="json_object", user=user
|
509
518
|
)
|
510
519
|
|
511
520
|
# Validate that the response is a non-empty, JSON-serializable list
|
@@ -524,7 +533,7 @@ async def generate_online_subqueries(
|
|
524
533
|
|
525
534
|
|
526
535
|
async def schedule_query(
|
527
|
-
q: str, conversation_history: dict, user: KhojUser,
|
536
|
+
q: str, conversation_history: dict, user: KhojUser, query_images: List[str] = None
|
528
537
|
) -> Tuple[str, ...]:
|
529
538
|
"""
|
530
539
|
Schedule the date, time to run the query. Assume the server timezone is UTC.
|
@@ -537,7 +546,7 @@ async def schedule_query(
|
|
537
546
|
)
|
538
547
|
|
539
548
|
raw_response = await send_message_to_model_wrapper(
|
540
|
-
crontime_prompt,
|
549
|
+
crontime_prompt, query_images=query_images, response_type="json_object", user=user
|
541
550
|
)
|
542
551
|
|
543
552
|
# Validate that the response is a non-empty, JSON-serializable list
|
@@ -583,7 +592,7 @@ async def extract_relevant_summary(
|
|
583
592
|
q: str,
|
584
593
|
corpus: str,
|
585
594
|
conversation_history: dict,
|
586
|
-
|
595
|
+
query_images: List[str] = None,
|
587
596
|
user: KhojUser = None,
|
588
597
|
agent: Agent = None,
|
589
598
|
) -> Union[str, None]:
|
@@ -612,11 +621,134 @@ async def extract_relevant_summary(
|
|
612
621
|
extract_relevant_information,
|
613
622
|
prompts.system_prompt_extract_relevant_summary,
|
614
623
|
user=user,
|
615
|
-
|
624
|
+
query_images=query_images,
|
616
625
|
)
|
617
626
|
return response.strip()
|
618
627
|
|
619
628
|
|
629
|
+
async def generate_excalidraw_diagram(
|
630
|
+
q: str,
|
631
|
+
conversation_history: Dict[str, Any],
|
632
|
+
location_data: LocationData,
|
633
|
+
note_references: List[Dict[str, Any]],
|
634
|
+
online_results: Optional[dict] = None,
|
635
|
+
query_images: List[str] = None,
|
636
|
+
user: KhojUser = None,
|
637
|
+
agent: Agent = None,
|
638
|
+
send_status_func: Optional[Callable] = None,
|
639
|
+
):
|
640
|
+
if send_status_func:
|
641
|
+
async for event in send_status_func("**Enhancing the Diagramming Prompt**"):
|
642
|
+
yield {ChatEvent.STATUS: event}
|
643
|
+
|
644
|
+
better_diagram_description_prompt = await generate_better_diagram_description(
|
645
|
+
q=q,
|
646
|
+
conversation_history=conversation_history,
|
647
|
+
location_data=location_data,
|
648
|
+
note_references=note_references,
|
649
|
+
online_results=online_results,
|
650
|
+
query_images=query_images,
|
651
|
+
user=user,
|
652
|
+
agent=agent,
|
653
|
+
)
|
654
|
+
|
655
|
+
if send_status_func:
|
656
|
+
async for event in send_status_func(f"**Diagram to Create:**:\n{better_diagram_description_prompt}"):
|
657
|
+
yield {ChatEvent.STATUS: event}
|
658
|
+
|
659
|
+
excalidraw_diagram_description = await generate_excalidraw_diagram_from_description(
|
660
|
+
q=better_diagram_description_prompt,
|
661
|
+
user=user,
|
662
|
+
agent=agent,
|
663
|
+
)
|
664
|
+
|
665
|
+
yield better_diagram_description_prompt, excalidraw_diagram_description
|
666
|
+
|
667
|
+
|
668
|
+
async def generate_better_diagram_description(
|
669
|
+
q: str,
|
670
|
+
conversation_history: Dict[str, Any],
|
671
|
+
location_data: LocationData,
|
672
|
+
note_references: List[Dict[str, Any]],
|
673
|
+
online_results: Optional[dict] = None,
|
674
|
+
query_images: List[str] = None,
|
675
|
+
user: KhojUser = None,
|
676
|
+
agent: Agent = None,
|
677
|
+
) -> str:
|
678
|
+
"""
|
679
|
+
Generate a diagram description from the given query and context
|
680
|
+
"""
|
681
|
+
|
682
|
+
today_date = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d, %A")
|
683
|
+
personality_context = (
|
684
|
+
prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else ""
|
685
|
+
)
|
686
|
+
|
687
|
+
if location_data:
|
688
|
+
location_prompt = prompts.user_location.format(location=f"{location_data}")
|
689
|
+
else:
|
690
|
+
location_prompt = "Unknown"
|
691
|
+
|
692
|
+
user_references = "\n\n".join([f"# {item['compiled']}" for item in note_references])
|
693
|
+
|
694
|
+
chat_history = construct_chat_history(conversation_history)
|
695
|
+
|
696
|
+
simplified_online_results = {}
|
697
|
+
|
698
|
+
if online_results:
|
699
|
+
for result in online_results:
|
700
|
+
if online_results[result].get("answerBox"):
|
701
|
+
simplified_online_results[result] = online_results[result]["answerBox"]
|
702
|
+
elif online_results[result].get("webpages"):
|
703
|
+
simplified_online_results[result] = online_results[result]["webpages"]
|
704
|
+
|
705
|
+
improve_diagram_description_prompt = prompts.improve_diagram_description_prompt.format(
|
706
|
+
query=q,
|
707
|
+
chat_history=chat_history,
|
708
|
+
location=location_prompt,
|
709
|
+
current_date=today_date,
|
710
|
+
references=user_references,
|
711
|
+
online_results=simplified_online_results,
|
712
|
+
personality_context=personality_context,
|
713
|
+
)
|
714
|
+
|
715
|
+
with timer("Chat actor: Generate better diagram description", logger):
|
716
|
+
response = await send_message_to_model_wrapper(
|
717
|
+
improve_diagram_description_prompt, query_images=query_images, user=user
|
718
|
+
)
|
719
|
+
response = response.strip()
|
720
|
+
if response.startswith(('"', "'")) and response.endswith(('"', "'")):
|
721
|
+
response = response[1:-1]
|
722
|
+
|
723
|
+
return response
|
724
|
+
|
725
|
+
|
726
|
+
async def generate_excalidraw_diagram_from_description(
|
727
|
+
q: str,
|
728
|
+
user: KhojUser = None,
|
729
|
+
agent: Agent = None,
|
730
|
+
) -> str:
|
731
|
+
personality_context = (
|
732
|
+
prompts.personality_context.format(personality=agent.personality) if agent and agent.personality else ""
|
733
|
+
)
|
734
|
+
|
735
|
+
excalidraw_diagram_generation = prompts.excalidraw_diagram_generation_prompt.format(
|
736
|
+
personality_context=personality_context,
|
737
|
+
query=q,
|
738
|
+
)
|
739
|
+
|
740
|
+
with timer("Chat actor: Generate excalidraw diagram", logger):
|
741
|
+
raw_response = await send_message_to_model_wrapper(message=excalidraw_diagram_generation, user=user)
|
742
|
+
raw_response = raw_response.strip()
|
743
|
+
raw_response = remove_json_codeblock(raw_response)
|
744
|
+
response: Dict[str, str] = json.loads(raw_response)
|
745
|
+
if not response or not isinstance(response, List) or not isinstance(response[0], Dict):
|
746
|
+
# TODO Some additional validation here that it's a valid Excalidraw diagram
|
747
|
+
raise AssertionError(f"Invalid response for improving diagram description: {response}")
|
748
|
+
|
749
|
+
return response
|
750
|
+
|
751
|
+
|
620
752
|
async def generate_better_image_prompt(
|
621
753
|
q: str,
|
622
754
|
conversation_history: str,
|
@@ -624,7 +756,7 @@ async def generate_better_image_prompt(
|
|
624
756
|
note_references: List[Dict[str, Any]],
|
625
757
|
online_results: Optional[dict] = None,
|
626
758
|
model_type: Optional[str] = None,
|
627
|
-
|
759
|
+
query_images: Optional[List[str]] = None,
|
628
760
|
user: KhojUser = None,
|
629
761
|
agent: Agent = None,
|
630
762
|
) -> str:
|
@@ -676,7 +808,7 @@ async def generate_better_image_prompt(
|
|
676
808
|
)
|
677
809
|
|
678
810
|
with timer("Chat actor: Generate contextual image prompt", logger):
|
679
|
-
response = await send_message_to_model_wrapper(image_prompt,
|
811
|
+
response = await send_message_to_model_wrapper(image_prompt, query_images=query_images, user=user)
|
680
812
|
response = response.strip()
|
681
813
|
if response.startswith(('"', "'")) and response.endswith(('"', "'")):
|
682
814
|
response = response[1:-1]
|
@@ -689,11 +821,11 @@ async def send_message_to_model_wrapper(
|
|
689
821
|
system_message: str = "",
|
690
822
|
response_type: str = "text",
|
691
823
|
user: KhojUser = None,
|
692
|
-
|
824
|
+
query_images: List[str] = None,
|
693
825
|
):
|
694
826
|
conversation_config: ChatModelOptions = await ConversationAdapters.aget_default_conversation_config(user)
|
695
827
|
vision_available = conversation_config.vision_enabled
|
696
|
-
if not vision_available and
|
828
|
+
if not vision_available and query_images:
|
697
829
|
vision_enabled_config = await ConversationAdapters.aget_vision_enabled_config()
|
698
830
|
if vision_enabled_config:
|
699
831
|
conversation_config = vision_enabled_config
|
@@ -746,7 +878,7 @@ async def send_message_to_model_wrapper(
|
|
746
878
|
max_prompt_size=max_tokens,
|
747
879
|
tokenizer_name=tokenizer,
|
748
880
|
vision_enabled=vision_available,
|
749
|
-
|
881
|
+
query_images=query_images,
|
750
882
|
model_type=conversation_config.model_type,
|
751
883
|
)
|
752
884
|
|
@@ -766,7 +898,7 @@ async def send_message_to_model_wrapper(
|
|
766
898
|
max_prompt_size=max_tokens,
|
767
899
|
tokenizer_name=tokenizer,
|
768
900
|
vision_enabled=vision_available,
|
769
|
-
|
901
|
+
query_images=query_images,
|
770
902
|
model_type=conversation_config.model_type,
|
771
903
|
)
|
772
904
|
|
@@ -784,7 +916,8 @@ async def send_message_to_model_wrapper(
|
|
784
916
|
max_prompt_size=max_tokens,
|
785
917
|
tokenizer_name=tokenizer,
|
786
918
|
vision_enabled=vision_available,
|
787
|
-
|
919
|
+
query_images=query_images,
|
920
|
+
model_type=conversation_config.model_type,
|
788
921
|
)
|
789
922
|
|
790
923
|
return gemini_send_message_to_model(
|
@@ -875,6 +1008,7 @@ def send_message_to_model_wrapper_sync(
|
|
875
1008
|
model_name=chat_model,
|
876
1009
|
max_prompt_size=max_tokens,
|
877
1010
|
vision_enabled=vision_available,
|
1011
|
+
model_type=conversation_config.model_type,
|
878
1012
|
)
|
879
1013
|
|
880
1014
|
return gemini_send_message_to_model(
|
@@ -900,7 +1034,7 @@ def generate_chat_response(
|
|
900
1034
|
conversation_id: str = None,
|
901
1035
|
location_data: LocationData = None,
|
902
1036
|
user_name: Optional[str] = None,
|
903
|
-
|
1037
|
+
query_images: Optional[List[str]] = None,
|
904
1038
|
) -> Tuple[Union[ThreadedGenerator, Iterator[str]], Dict[str, str]]:
|
905
1039
|
# Initialize Variables
|
906
1040
|
chat_response = None
|
@@ -919,12 +1053,12 @@ def generate_chat_response(
|
|
919
1053
|
inferred_queries=inferred_queries,
|
920
1054
|
client_application=client_application,
|
921
1055
|
conversation_id=conversation_id,
|
922
|
-
|
1056
|
+
query_images=query_images,
|
923
1057
|
)
|
924
1058
|
|
925
1059
|
conversation_config = ConversationAdapters.get_valid_conversation_config(user, conversation)
|
926
1060
|
vision_available = conversation_config.vision_enabled
|
927
|
-
if not vision_available and
|
1061
|
+
if not vision_available and query_images:
|
928
1062
|
vision_enabled_config = ConversationAdapters.get_vision_enabled_config()
|
929
1063
|
if vision_enabled_config:
|
930
1064
|
conversation_config = vision_enabled_config
|
@@ -955,7 +1089,7 @@ def generate_chat_response(
|
|
955
1089
|
chat_response = converse(
|
956
1090
|
compiled_references,
|
957
1091
|
q,
|
958
|
-
|
1092
|
+
query_images=query_images,
|
959
1093
|
online_results=online_results,
|
960
1094
|
conversation_log=meta_log,
|
961
1095
|
model=chat_model,
|
@@ -993,8 +1127,9 @@ def generate_chat_response(
|
|
993
1127
|
chat_response = converse_gemini(
|
994
1128
|
compiled_references,
|
995
1129
|
q,
|
996
|
-
|
997
|
-
|
1130
|
+
query_images=query_images,
|
1131
|
+
online_results=online_results,
|
1132
|
+
conversation_log=meta_log,
|
998
1133
|
model=conversation_config.chat_model,
|
999
1134
|
api_key=api_key,
|
1000
1135
|
completion_func=partial_completion,
|
@@ -1004,6 +1139,7 @@ def generate_chat_response(
|
|
1004
1139
|
location_data=location_data,
|
1005
1140
|
user_name=user_name,
|
1006
1141
|
agent=agent,
|
1142
|
+
vision_available=vision_available,
|
1007
1143
|
)
|
1008
1144
|
|
1009
1145
|
metadata.update({"chat_model": conversation_config.chat_model})
|
@@ -1015,6 +1151,22 @@ def generate_chat_response(
|
|
1015
1151
|
return chat_response, metadata
|
1016
1152
|
|
1017
1153
|
|
1154
|
+
class ChatRequestBody(BaseModel):
|
1155
|
+
q: str
|
1156
|
+
n: Optional[int] = 7
|
1157
|
+
d: Optional[float] = None
|
1158
|
+
stream: Optional[bool] = False
|
1159
|
+
title: Optional[str] = None
|
1160
|
+
conversation_id: Optional[str] = None
|
1161
|
+
city: Optional[str] = None
|
1162
|
+
region: Optional[str] = None
|
1163
|
+
country: Optional[str] = None
|
1164
|
+
country_code: Optional[str] = None
|
1165
|
+
timezone: Optional[str] = None
|
1166
|
+
images: Optional[list[str]] = None
|
1167
|
+
create_new: Optional[bool] = False
|
1168
|
+
|
1169
|
+
|
1018
1170
|
class ApiUserRateLimiter:
|
1019
1171
|
def __init__(self, requests: int, subscribed_requests: int, window: int, slug: str):
|
1020
1172
|
self.requests = requests
|
@@ -1060,13 +1212,58 @@ class ApiUserRateLimiter:
|
|
1060
1212
|
)
|
1061
1213
|
raise HTTPException(
|
1062
1214
|
status_code=429,
|
1063
|
-
detail="
|
1215
|
+
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
1216
|
)
|
1065
1217
|
|
1066
1218
|
# Add the current request to the cache
|
1067
1219
|
UserRequests.objects.create(user=user, slug=self.slug)
|
1068
1220
|
|
1069
1221
|
|
1222
|
+
class ApiImageRateLimiter:
|
1223
|
+
def __init__(self, max_images: int = 10, max_combined_size_mb: float = 10):
|
1224
|
+
self.max_images = max_images
|
1225
|
+
self.max_combined_size_mb = max_combined_size_mb
|
1226
|
+
|
1227
|
+
def __call__(self, request: Request, body: ChatRequestBody):
|
1228
|
+
if state.billing_enabled is False:
|
1229
|
+
return
|
1230
|
+
|
1231
|
+
# Rate limiting is disabled if user unauthenticated.
|
1232
|
+
# Other systems handle authentication
|
1233
|
+
if not request.user.is_authenticated:
|
1234
|
+
return
|
1235
|
+
|
1236
|
+
if not body.images:
|
1237
|
+
return
|
1238
|
+
|
1239
|
+
# Check number of images
|
1240
|
+
if len(body.images) > self.max_images:
|
1241
|
+
raise HTTPException(
|
1242
|
+
status_code=429,
|
1243
|
+
detail=f"Those are way too many images for me! I can handle up to {self.max_images} images per message.",
|
1244
|
+
)
|
1245
|
+
|
1246
|
+
# Check total size of images
|
1247
|
+
total_size_mb = 0.0
|
1248
|
+
for image in body.images:
|
1249
|
+
# Unquote the image in case it's URL encoded
|
1250
|
+
image = unquote(image)
|
1251
|
+
# Assuming the image is a base64 encoded string
|
1252
|
+
# Remove the data:image/jpeg;base64, part if present
|
1253
|
+
if "," in image:
|
1254
|
+
image = image.split(",", 1)[1]
|
1255
|
+
|
1256
|
+
# Decode base64 to get the actual size
|
1257
|
+
image_bytes = base64.b64decode(image)
|
1258
|
+
total_size_mb += len(image_bytes) / (1024 * 1024) # Convert bytes to MB
|
1259
|
+
|
1260
|
+
if total_size_mb > self.max_combined_size_mb:
|
1261
|
+
raise HTTPException(
|
1262
|
+
status_code=429,
|
1263
|
+
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.",
|
1264
|
+
)
|
1265
|
+
|
1266
|
+
|
1070
1267
|
class ConversationCommandRateLimiter:
|
1071
1268
|
def __init__(self, trial_rate_limit: int, subscribed_rate_limit: int, slug: str):
|
1072
1269
|
self.slug = slug
|
@@ -1477,10 +1674,16 @@ def get_user_config(user: KhojUser, request: Request, is_detailed: bool = False)
|
|
1477
1674
|
|
1478
1675
|
user_subscription_state = get_user_subscription_state(user.email)
|
1479
1676
|
user_subscription = adapters.get_user_subscription(user.email)
|
1677
|
+
|
1480
1678
|
subscription_renewal_date = (
|
1481
1679
|
user_subscription.renewal_date.strftime("%d %b %Y")
|
1482
1680
|
if user_subscription and user_subscription.renewal_date
|
1483
|
-
else
|
1681
|
+
else None
|
1682
|
+
)
|
1683
|
+
subscription_enabled_trial_at = (
|
1684
|
+
user_subscription.enabled_trial_at.strftime("%d %b %Y")
|
1685
|
+
if user_subscription and user_subscription.enabled_trial_at
|
1686
|
+
else None
|
1484
1687
|
)
|
1485
1688
|
given_name = get_user_name(user)
|
1486
1689
|
|
@@ -1553,6 +1756,7 @@ def get_user_config(user: KhojUser, request: Request, is_detailed: bool = False)
|
|
1553
1756
|
# user billing info
|
1554
1757
|
"subscription_state": user_subscription_state,
|
1555
1758
|
"subscription_renewal_date": subscription_renewal_date,
|
1759
|
+
"subscription_enabled_trial_at": subscription_enabled_trial_at,
|
1556
1760
|
# server settings
|
1557
1761
|
"khoj_cloud_subscription_url": os.getenv("KHOJ_CLOUD_SUBSCRIPTION_URL"),
|
1558
1762
|
"billing_enabled": state.billing_enabled,
|
@@ -1561,6 +1765,7 @@ def get_user_config(user: KhojUser, request: Request, is_detailed: bool = False)
|
|
1561
1765
|
"khoj_version": state.khoj_version,
|
1562
1766
|
"anonymous_mode": state.anonymous_mode,
|
1563
1767
|
"notion_oauth_url": notion_oauth_url,
|
1768
|
+
"length_of_free_trial": LENGTH_OF_FREE_TRIAL,
|
1564
1769
|
}
|
1565
1770
|
|
1566
1771
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: khoj
|
3
|
-
Version: 1.26.
|
3
|
+
Version: 1.26.5.dev34
|
4
4
|
Summary: Your Second Brain
|
5
5
|
Project-URL: Homepage, https://khoj.dev
|
6
6
|
Project-URL: Documentation, https://docs.khoj.dev
|
@@ -56,7 +56,7 @@ Requires-Dist: pillow~=10.0.0
|
|
56
56
|
Requires-Dist: psutil>=5.8.0
|
57
57
|
Requires-Dist: psycopg2-binary==2.9.9
|
58
58
|
Requires-Dist: pydantic[email]>=2.0.0
|
59
|
-
Requires-Dist: pymupdf
|
59
|
+
Requires-Dist: pymupdf==1.24.11
|
60
60
|
Requires-Dist: python-multipart>=0.0.7
|
61
61
|
Requires-Dist: pytz~=2024.1
|
62
62
|
Requires-Dist: pyyaml~=6.0
|