khoj 1.20.3__py3-none-any.whl → 1.20.5.dev10__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 (78) hide show
  1. khoj/configure.py +1 -0
  2. khoj/database/adapters/__init__.py +30 -11
  3. khoj/database/migrations/0056_searchmodelconfig_cross_encoder_model_config.py +17 -0
  4. khoj/database/models/__init__.py +2 -0
  5. khoj/interface/compiled/404/index.html +1 -1
  6. khoj/interface/compiled/_next/static/chunks/9178-ef3257c08d8973c8.js +1 -0
  7. khoj/interface/compiled/_next/static/chunks/app/agents/{page-922694b75f1fb67b.js → page-1ac024e05374f91f.js} +1 -1
  8. khoj/interface/compiled/_next/static/chunks/app/automations/page-176fd855ffabf08e.js +1 -0
  9. khoj/interface/compiled/_next/static/chunks/app/chat/{page-51ab7c4b766ff344.js → page-8f7915867c7e42ba.js} +1 -1
  10. khoj/interface/compiled/_next/static/chunks/app/factchecker/{page-60be5e3295e2c0bc.js → page-a6d96a122a65feb5.js} +1 -1
  11. khoj/interface/compiled/_next/static/chunks/app/{page-635b30b582201b05.js → page-59e25010247e3780.js} +1 -1
  12. khoj/interface/compiled/_next/static/chunks/app/search/{page-dcd385f03255ef36.js → page-fde8c956cc33a187.js} +1 -1
  13. khoj/interface/compiled/_next/static/chunks/app/settings/{page-e83f6fa32691ca64.js → page-ab10cb1c576956fc.js} +1 -1
  14. khoj/interface/compiled/_next/static/chunks/app/share/chat/{page-699b364dc6fbf139.js → page-de1049b418d21444.js} +1 -1
  15. khoj/interface/compiled/_next/static/chunks/{webpack-97d9516936918753.js → webpack-f162a207b26413cd.js} +1 -1
  16. khoj/interface/compiled/_next/static/css/17e284bae7dc4881.css +1 -0
  17. khoj/interface/compiled/_next/static/css/2602b0918043c1e5.css +1 -0
  18. khoj/interface/compiled/_next/static/css/592ca99f5122e75a.css +1 -0
  19. khoj/interface/compiled/_next/static/css/6bde1f2045622ef7.css +1 -0
  20. khoj/interface/compiled/_next/static/css/9d5b867ec04494a6.css +25 -0
  21. khoj/interface/compiled/agents/index.html +1 -1
  22. khoj/interface/compiled/agents/index.txt +3 -3
  23. khoj/interface/compiled/assets/icons/khoj_lantern_logomarktype_1200x630.png +0 -0
  24. khoj/interface/compiled/automations/index.html +1 -1
  25. khoj/interface/compiled/automations/index.txt +3 -3
  26. khoj/interface/compiled/chat/index.html +1 -1
  27. khoj/interface/compiled/chat/index.txt +3 -3
  28. khoj/interface/compiled/factchecker/index.html +1 -1
  29. khoj/interface/compiled/factchecker/index.txt +3 -3
  30. khoj/interface/compiled/index.html +1 -1
  31. khoj/interface/compiled/index.txt +3 -3
  32. khoj/interface/compiled/khoj.webmanifest +51 -0
  33. khoj/interface/compiled/search/index.html +1 -1
  34. khoj/interface/compiled/search/index.txt +3 -3
  35. khoj/interface/compiled/settings/index.html +1 -1
  36. khoj/interface/compiled/settings/index.txt +3 -3
  37. khoj/interface/compiled/share/chat/index.html +1 -1
  38. khoj/interface/compiled/share/chat/index.txt +3 -3
  39. khoj/interface/web/base_config.html +1 -1
  40. khoj/interface/web/login.html +2 -2
  41. khoj/processor/conversation/anthropic/anthropic_chat.py +6 -2
  42. khoj/processor/conversation/offline/chat_model.py +15 -3
  43. khoj/processor/conversation/openai/gpt.py +6 -3
  44. khoj/processor/conversation/openai/utils.py +3 -1
  45. khoj/processor/conversation/prompts.py +20 -0
  46. khoj/processor/embeddings.py +3 -1
  47. khoj/processor/tools/online_search.py +9 -3
  48. khoj/routers/api.py +21 -4
  49. khoj/routers/api_chat.py +3 -3
  50. khoj/routers/email.py +2 -1
  51. khoj/routers/helpers.py +31 -6
  52. khoj/search_filter/file_filter.py +5 -2
  53. {khoj-1.20.3.dist-info → khoj-1.20.5.dev10.dist-info}/METADATA +1 -1
  54. {khoj-1.20.3.dist-info → khoj-1.20.5.dev10.dist-info}/RECORD +70 -68
  55. khoj/interface/compiled/_next/static/chunks/9178-859894e0d9d67aa5.js +0 -1
  56. khoj/interface/compiled/_next/static/chunks/app/automations/page-46913728bcb8f4c6.js +0 -1
  57. khoj/interface/compiled/_next/static/css/1de368beed21dfba.css +0 -25
  58. khoj/interface/compiled/_next/static/css/2272c73fc7a3b571.css +0 -1
  59. khoj/interface/compiled/_next/static/css/553f9cdcc7a2bcd6.css +0 -1
  60. khoj/interface/compiled/_next/static/css/a3530ec58b0b660f.css +0 -1
  61. khoj/interface/compiled/_next/static/css/df6f4c34ec280d53.css +0 -1
  62. khoj/interface/web/khoj.webmanifest +0 -51
  63. /khoj/interface/compiled/_next/static/chunks/{8423-132ea64eac83fd43.js → 8423-898d821eaab634af.js} +0 -0
  64. /khoj/interface/compiled/_next/static/chunks/{9417-2e54c6fd056982d8.js → 9417-5d14ac74aaab2c66.js} +0 -0
  65. /khoj/interface/compiled/_next/static/{iCI2Ycgat9pFGG9HeJvvH → n2f7OOHpHHaOEKtsWrb_9}/_buildManifest.js +0 -0
  66. /khoj/interface/compiled/_next/static/{iCI2Ycgat9pFGG9HeJvvH → n2f7OOHpHHaOEKtsWrb_9}/_ssgManifest.js +0 -0
  67. /khoj/interface/compiled/{favicon.ico → assets/icons/khoj_lantern.ico} +0 -0
  68. /khoj/interface/{web/assets/icons/favicon-128x128.png → compiled/assets/icons/khoj_lantern_128x128.png} +0 -0
  69. /khoj/interface/{web/assets/icons/favicon-256x256.png → compiled/assets/icons/khoj_lantern_256x256.png} +0 -0
  70. /khoj/interface/{web → compiled}/assets/samples/desktop-browse-draw-sample.png +0 -0
  71. /khoj/interface/{web → compiled}/assets/samples/desktop-plain-chat-sample.png +0 -0
  72. /khoj/interface/{web → compiled}/assets/samples/desktop-remember-plan-sample.png +0 -0
  73. /khoj/interface/{web → compiled}/assets/samples/phone-browse-draw-sample.png +0 -0
  74. /khoj/interface/{web → compiled}/assets/samples/phone-plain-chat-sample.png +0 -0
  75. /khoj/interface/{web → compiled}/assets/samples/phone-remember-plan-sample.png +0 -0
  76. {khoj-1.20.3.dist-info → khoj-1.20.5.dev10.dist-info}/WHEEL +0 -0
  77. {khoj-1.20.3.dist-info → khoj-1.20.5.dev10.dist-info}/entry_points.txt +0 -0
  78. {khoj-1.20.3.dist-info → khoj-1.20.5.dev10.dist-info}/licenses/LICENSE +0 -0
@@ -208,10 +208,12 @@ Construct search queries to retrieve relevant information to answer the user's q
208
208
  - Add as much context from the previous questions and answers as required into your search queries.
209
209
  - Break messages into multiple search queries when required to retrieve the relevant information.
210
210
  - Add date filters to your search queries from questions and answers when required to retrieve the relevant information.
211
+ - When asked a meta, vague or random questions, search for a variety of broad topics to answer the user's question.
211
212
  - Share relevant search queries as a JSON list of strings. Do not say anything else.
212
213
 
213
214
  Current Date: {day_of_week}, {current_date}
214
215
  User's Location: {location}
216
+ {username}
215
217
 
216
218
  Examples:
217
219
  Q: How was my trip to Cambodia?
@@ -238,6 +240,9 @@ Khoj: ["What kind of plants do I have?", "What issues do my plants have?"]
238
240
  Q: Who all did I meet here yesterday?
239
241
  Khoj: ["Met in {location} on {yesterday_date} dt>='{yesterday_date}' dt<'{current_date}'"]
240
242
 
243
+ Q: Share some random, interesting experiences from this month
244
+ Khoj: ["Exciting travel adventures from {current_month}", "Fun social events dt>='{current_month}-01' dt<'{current_date}'", "Intense emotional experiences in {current_month}"]
245
+
241
246
  Chat History:
242
247
  {chat_history}
243
248
  What searches will you perform to answer the following question, using the chat history as reference? Respond only with relevant search queries as a valid JSON list of strings.
@@ -254,10 +259,12 @@ Construct search queries to retrieve relevant information to answer the user's q
254
259
  - Add as much context from the previous questions and answers as required into your search queries.
255
260
  - Break messages into multiple search queries when required to retrieve the relevant information.
256
261
  - Add date filters to your search queries from questions and answers when required to retrieve the relevant information.
262
+ - When asked a meta, vague or random questions, search for a variety of broad topics to answer the user's question.
257
263
 
258
264
  What searches will you perform to answer the users question? Respond with search queries as list of strings in a JSON object.
259
265
  Current Date: {day_of_week}, {current_date}
260
266
  User's Location: {location}
267
+ {username}
261
268
 
262
269
  Q: How was my trip to Cambodia?
263
270
  Khoj: {{"queries": ["How was my trip to Cambodia?"]}}
@@ -279,6 +286,10 @@ Q: How many tennis balls fit in the back of a 2002 Honda Civic?
279
286
  Khoj: {{"queries": ["What is the size of a tennis ball?", "What is the trunk size of a 2002 Honda Civic?"]}}
280
287
  A: 1085 tennis balls will fit in the trunk of a Honda Civic
281
288
 
289
+ Q: Share some random, interesting experiences from this month
290
+ Khoj: {{"queries": ["Exciting travel adventures from {current_month}", "Fun social events dt>='{current_month}-01' dt<'{current_date}'", "Intense emotional experiences in {current_month}"]}}
291
+ A: You had a great time at the local beach with your friends, attended a music concert and had a deep conversation with your friend, Khalid.
292
+
282
293
  Q: Is Bob older than Tom?
283
294
  Khoj: {{"queries": ["When was Bob born?", "What is Tom's age?"]}}
284
295
  A: Yes, Bob is older than Tom. As Bob was born on 1984-01-01 and Tom is 30 years old.
@@ -305,11 +316,13 @@ Construct search queries to retrieve relevant information to answer the user's q
305
316
  - Add as much context from the previous questions and answers as required into your search queries.
306
317
  - Break messages into multiple search queries when required to retrieve the relevant information.
307
318
  - Add date filters to your search queries from questions and answers when required to retrieve the relevant information.
319
+ - When asked a meta, vague or random questions, search for a variety of broad topics to answer the user's question.
308
320
 
309
321
  What searches will you perform to answer the users question? Respond with a JSON object with the key "queries" mapping to a list of searches you would perform on the user's knowledge base. Just return the queries and nothing else.
310
322
 
311
323
  Current Date: {day_of_week}, {current_date}
312
324
  User's Location: {location}
325
+ {username}
313
326
 
314
327
  Here are some examples of how you can construct search queries to answer the user's question:
315
328
 
@@ -328,6 +341,11 @@ A: I can help you live healthier and happier across work and personal life
328
341
  User: Who all did I meet here yesterday?
329
342
  Assistant: {{"queries": ["Met in {location} on {yesterday_date} dt>='{yesterday_date}' dt<'{current_date}'"]}}
330
343
  A: Yesterday's note mentions your visit to your local beach with Ram and Shyam.
344
+
345
+ User: Share some random, interesting experiences from this month
346
+ Assistant: {{"queries": ["Exciting travel adventures from {current_month}", "Fun social events dt>='{current_month}-01' dt<'{current_date}'", "Intense emotional experiences in {current_month}"]}}
347
+ A: You had a great time at the local beach with your friends, attended a music concert and had a deep conversation with your friend, Khalid.
348
+
331
349
  """.strip()
332
350
  )
333
351
 
@@ -525,6 +543,7 @@ Which webpages will you need to read to answer the user's question?
525
543
  Provide web page links as a list of strings in a JSON object.
526
544
  Current Date: {current_date}
527
545
  User's Location: {location}
546
+ {username}
528
547
 
529
548
  Here are some examples:
530
549
  History:
@@ -571,6 +590,7 @@ What Google searches, if any, will you need to perform to answer the user's ques
571
590
  Provide search queries as a list of strings in a JSON object. Do not wrap the json in a codeblock.
572
591
  Current Date: {current_date}
573
592
  User's Location: {location}
593
+ {username}
574
594
 
575
595
  Here are some examples:
576
596
  History:
@@ -95,11 +95,13 @@ class CrossEncoderModel:
95
95
  model_name: str = "mixedbread-ai/mxbai-rerank-xsmall-v1",
96
96
  cross_encoder_inference_endpoint: str = None,
97
97
  cross_encoder_inference_endpoint_api_key: str = None,
98
+ model_kwargs: dict = {},
98
99
  ):
99
100
  self.model_name = model_name
100
- self.cross_encoder_model = CrossEncoder(model_name=self.model_name, device=get_device())
101
101
  self.inference_endpoint = cross_encoder_inference_endpoint
102
102
  self.api_key = cross_encoder_inference_endpoint_api_key
103
+ self.model_kwargs = merge_dicts(model_kwargs, {"device": get_device()})
104
+ self.cross_encoder_model = CrossEncoder(model_name=self.model_name, **self.model_kwargs)
103
105
 
104
106
  def inference_server_enabled(self) -> bool:
105
107
  return self.api_key is not None and self.inference_endpoint is not None
@@ -10,6 +10,7 @@ import aiohttp
10
10
  from bs4 import BeautifulSoup
11
11
  from markdownify import markdownify
12
12
 
13
+ from khoj.database.models import KhojUser
13
14
  from khoj.routers.helpers import (
14
15
  ChatEvent,
15
16
  extract_relevant_info,
@@ -51,6 +52,7 @@ async def search_online(
51
52
  query: str,
52
53
  conversation_history: dict,
53
54
  location: LocationData,
55
+ user: KhojUser,
54
56
  send_status_func: Optional[Callable] = None,
55
57
  custom_filters: List[str] = [],
56
58
  ):
@@ -61,7 +63,7 @@ async def search_online(
61
63
  return
62
64
 
63
65
  # Breakdown the query into subqueries to get the correct answer
64
- subqueries = await generate_online_subqueries(query, conversation_history, location)
66
+ subqueries = await generate_online_subqueries(query, conversation_history, location, user)
65
67
  response_dict = {}
66
68
 
67
69
  if subqueries:
@@ -126,14 +128,18 @@ async def search_with_google(query: str) -> Tuple[str, Dict[str, List[Dict]]]:
126
128
 
127
129
 
128
130
  async def read_webpages(
129
- query: str, conversation_history: dict, location: LocationData, send_status_func: Optional[Callable] = None
131
+ query: str,
132
+ conversation_history: dict,
133
+ location: LocationData,
134
+ user: KhojUser,
135
+ send_status_func: Optional[Callable] = None,
130
136
  ):
131
137
  "Infer web pages to read from the query and extract relevant information from them"
132
138
  logger.info(f"Inferring web pages to read")
133
139
  if send_status_func:
134
140
  async for event in send_status_func(f"**Inferring web pages to read**"):
135
141
  yield {ChatEvent.STATUS: event}
136
- urls = await infer_webpage_urls(query, conversation_history, location)
142
+ urls = await infer_webpage_urls(query, conversation_history, location, user)
137
143
 
138
144
  logger.info(f"Reading web pages at: {urls}")
139
145
  if send_status_func:
khoj/routers/api.py CHANGED
@@ -388,6 +388,7 @@ async def extract_references_and_questions(
388
388
  conversation_log=meta_log,
389
389
  should_extract_questions=True,
390
390
  location_data=location_data,
391
+ user=user,
391
392
  max_prompt_size=conversation_config.max_prompt_size,
392
393
  )
393
394
  elif conversation_config.model_type == ChatModelOptions.ModelType.OPENAI:
@@ -402,7 +403,7 @@ async def extract_references_and_questions(
402
403
  api_base_url=base_url,
403
404
  conversation_log=meta_log,
404
405
  location_data=location_data,
405
- max_tokens=conversation_config.max_prompt_size,
406
+ user=user,
406
407
  )
407
408
  elif conversation_config.model_type == ChatModelOptions.ModelType.ANTHROPIC:
408
409
  api_key = conversation_config.openai_config.api_key
@@ -413,6 +414,7 @@ async def extract_references_and_questions(
413
414
  api_key=api_key,
414
415
  conversation_log=meta_log,
415
416
  location_data=location_data,
417
+ user=user,
416
418
  )
417
419
 
418
420
  # Collate search results as context for GPT
@@ -545,15 +547,19 @@ async def post_automation(
545
547
  if not subject:
546
548
  subject = await acreate_title_from_query(q)
547
549
 
550
+ title = f"Automation: {subject}"
551
+
548
552
  # Create new Conversation Session associated with this new task
549
- conversation = await ConversationAdapters.acreate_conversation_session(user, request.user.client_app)
553
+ conversation = await ConversationAdapters.acreate_conversation_session(user, request.user.client_app, title=title)
550
554
 
551
- calling_url = request.url.replace(query=f"{request.url.query}&conversation_id={conversation.id}")
555
+ calling_url = request.url.replace(query=f"{request.url.query}")
552
556
 
553
557
  # Schedule automation with query_to_run, timezone, subject directly provided by user
554
558
  try:
555
559
  # Use the query to run as the scheduling request if the scheduling request is unset
556
- automation = await schedule_automation(query_to_run, subject, crontime, timezone, q, user, calling_url)
560
+ automation = await schedule_automation(
561
+ query_to_run, subject, crontime, timezone, q, user, calling_url, conversation.id
562
+ )
557
563
  except Exception as e:
558
564
  logger.error(f"Error creating automation {q} for {user.email}: {e}", exc_info=True)
559
565
  return Response(
@@ -649,6 +655,16 @@ def edit_job(
649
655
  automation_metadata["query_to_run"] = query_to_run
650
656
  automation_metadata["subject"] = subject.strip()
651
657
  automation_metadata["crontime"] = crontime
658
+ conversation_id = automation_metadata.get("conversation_id")
659
+
660
+ if not conversation_id:
661
+ title = f"Automation: {subject}"
662
+
663
+ # Create new Conversation Session associated with this new task
664
+ conversation = ConversationAdapters.create_conversation_session(user, request.user.client_app, title=title)
665
+
666
+ conversation_id = conversation.id
667
+ automation_metadata["conversation_id"] = conversation_id
652
668
 
653
669
  # Modify automation with updated query, subject
654
670
  automation.modify(
@@ -659,6 +675,7 @@ def edit_job(
659
675
  "scheduling_request": q,
660
676
  "user": user,
661
677
  "calling_url": request.url,
678
+ "conversation_id": conversation_id,
662
679
  },
663
680
  )
664
681
 
khoj/routers/api_chat.py CHANGED
@@ -146,7 +146,7 @@ async def sendfeedback(request: Request, data: FeedbackData):
146
146
 
147
147
 
148
148
  @api_chat.post("/speech")
149
- @requires(["authenticated", "premium"])
149
+ @requires(["authenticated"])
150
150
  async def text_to_speech(
151
151
  request: Request,
152
152
  common: CommonQueryParams,
@@ -792,7 +792,7 @@ async def chat(
792
792
  if ConversationCommand.Online in conversation_commands:
793
793
  try:
794
794
  async for result in search_online(
795
- defiltered_query, meta_log, location, partial(send_event, ChatEvent.STATUS), custom_filters
795
+ defiltered_query, meta_log, location, user, partial(send_event, ChatEvent.STATUS), custom_filters
796
796
  ):
797
797
  if isinstance(result, dict) and ChatEvent.STATUS in result:
798
798
  yield result[ChatEvent.STATUS]
@@ -809,7 +809,7 @@ async def chat(
809
809
  if ConversationCommand.Webpage in conversation_commands:
810
810
  try:
811
811
  async for result in read_webpages(
812
- defiltered_query, meta_log, location, partial(send_event, ChatEvent.STATUS)
812
+ defiltered_query, meta_log, location, user, partial(send_event, ChatEvent.STATUS)
813
813
  ):
814
814
  if isinstance(result, dict) and ChatEvent.STATUS in result:
815
815
  yield result[ChatEvent.STATUS]
khoj/routers/email.py CHANGED
@@ -117,7 +117,8 @@ def send_task_email(name, email, query, result, subject, is_image=False):
117
117
  template = env.get_template("task.html")
118
118
 
119
119
  if is_image:
120
- result = f"![{subject}]({result})"
120
+ image = result.get("image")
121
+ result = f"![{subject}]({image})"
121
122
 
122
123
  html_result = markdown_it.MarkdownIt().render(result)
123
124
  html_content = template.render(name=name, subject=subject, query=query, result=html_result)
khoj/routers/helpers.py CHANGED
@@ -340,11 +340,14 @@ async def aget_relevant_output_modes(query: str, conversation_history: dict, is_
340
340
  return ConversationCommand.Text
341
341
 
342
342
 
343
- async def infer_webpage_urls(q: str, conversation_history: dict, location_data: LocationData) -> List[str]:
343
+ async def infer_webpage_urls(
344
+ q: str, conversation_history: dict, location_data: LocationData, user: KhojUser
345
+ ) -> List[str]:
344
346
  """
345
347
  Infer webpage links from the given query
346
348
  """
347
349
  location = f"{location_data.city}, {location_data.region}, {location_data.country}" if location_data else "Unknown"
350
+ username = prompts.user_name.format(name=user.get_full_name()) if user.get_full_name() else ""
348
351
  chat_history = construct_chat_history(conversation_history)
349
352
 
350
353
  utc_date = datetime.utcnow().strftime("%Y-%m-%d")
@@ -353,6 +356,7 @@ async def infer_webpage_urls(q: str, conversation_history: dict, location_data:
353
356
  query=q,
354
357
  chat_history=chat_history,
355
358
  location=location,
359
+ username=username,
356
360
  )
357
361
 
358
362
  with timer("Chat actor: Infer webpage urls to read", logger):
@@ -370,11 +374,14 @@ async def infer_webpage_urls(q: str, conversation_history: dict, location_data:
370
374
  raise ValueError(f"Invalid list of urls: {response}")
371
375
 
372
376
 
373
- async def generate_online_subqueries(q: str, conversation_history: dict, location_data: LocationData) -> List[str]:
377
+ async def generate_online_subqueries(
378
+ q: str, conversation_history: dict, location_data: LocationData, user: KhojUser
379
+ ) -> List[str]:
374
380
  """
375
381
  Generate subqueries from the given query
376
382
  """
377
383
  location = f"{location_data.city}, {location_data.region}, {location_data.country}" if location_data else "Unknown"
384
+ username = prompts.user_name.format(name=user.get_full_name()) if user.get_full_name() else ""
378
385
  chat_history = construct_chat_history(conversation_history)
379
386
 
380
387
  utc_date = datetime.utcnow().strftime("%Y-%m-%d")
@@ -383,6 +390,7 @@ async def generate_online_subqueries(q: str, conversation_history: dict, locatio
383
390
  query=q,
384
391
  chat_history=chat_history,
385
392
  location=location,
393
+ username=username,
386
394
  )
387
395
 
388
396
  with timer("Chat actor: Generate online search subqueries", logger):
@@ -1074,7 +1082,13 @@ def should_notify(original_query: str, executed_query: str, ai_response: str) ->
1074
1082
 
1075
1083
 
1076
1084
  def scheduled_chat(
1077
- query_to_run: str, scheduling_request: str, subject: str, user: KhojUser, calling_url: URL, job_id: str = None
1085
+ query_to_run: str,
1086
+ scheduling_request: str,
1087
+ subject: str,
1088
+ user: KhojUser,
1089
+ calling_url: URL,
1090
+ job_id: str = None,
1091
+ conversation_id: int = None,
1078
1092
  ):
1079
1093
  logger.info(f"Processing scheduled_chat: {query_to_run}")
1080
1094
  if job_id:
@@ -1101,6 +1115,10 @@ def scheduled_chat(
1101
1115
  # Replace the original scheduling query with the scheduled query
1102
1116
  query_dict["q"] = [query_to_run]
1103
1117
 
1118
+ # Replace the original conversation_id with the conversation_id
1119
+ if conversation_id:
1120
+ query_dict["conversation_id"] = [conversation_id]
1121
+
1104
1122
  # Construct the URL to call the chat API with the scheduled query string
1105
1123
  encoded_query = urlencode(query_dict, doseq=True)
1106
1124
  url = f"{scheme}://{calling_url.netloc}/api/chat?{encoded_query}"
@@ -1130,7 +1148,9 @@ def scheduled_chat(
1130
1148
  if raw_response.headers.get("Content-Type") == "application/json":
1131
1149
  response_map = raw_response.json()
1132
1150
  ai_response = response_map.get("response") or response_map.get("image")
1133
- is_image = response_map.get("image") is not None
1151
+ is_image = False
1152
+ if type(ai_response) == dict:
1153
+ is_image = ai_response.get("image") is not None
1134
1154
  else:
1135
1155
  ai_response = raw_response.text
1136
1156
 
@@ -1142,9 +1162,11 @@ def scheduled_chat(
1142
1162
  return raw_response
1143
1163
 
1144
1164
 
1145
- async def create_automation(q: str, timezone: str, user: KhojUser, calling_url: URL, meta_log: dict = {}):
1165
+ async def create_automation(
1166
+ q: str, timezone: str, user: KhojUser, calling_url: URL, meta_log: dict = {}, conversation_id: int = None
1167
+ ):
1146
1168
  crontime, query_to_run, subject = await schedule_query(q, meta_log)
1147
- job = await schedule_automation(query_to_run, subject, crontime, timezone, q, user, calling_url)
1169
+ job = await schedule_automation(query_to_run, subject, crontime, timezone, q, user, calling_url, conversation_id)
1148
1170
  return job, crontime, query_to_run, subject
1149
1171
 
1150
1172
 
@@ -1156,6 +1178,7 @@ async def schedule_automation(
1156
1178
  scheduling_request: str,
1157
1179
  user: KhojUser,
1158
1180
  calling_url: URL,
1181
+ conversation_id: int,
1159
1182
  ):
1160
1183
  # Disable minute level automation recurrence
1161
1184
  minute_value = crontime.split(" ")[0]
@@ -1173,6 +1196,7 @@ async def schedule_automation(
1173
1196
  "scheduling_request": scheduling_request,
1174
1197
  "subject": subject,
1175
1198
  "crontime": crontime,
1199
+ "conversation_id": conversation_id,
1176
1200
  }
1177
1201
  )
1178
1202
  query_id = hashlib.md5(f"{query_to_run}_{crontime}".encode("utf-8")).hexdigest()
@@ -1191,6 +1215,7 @@ async def schedule_automation(
1191
1215
  "user": user,
1192
1216
  "calling_url": calling_url,
1193
1217
  "job_id": job_id,
1218
+ "conversation_id": conversation_id,
1194
1219
  },
1195
1220
  id=job_id,
1196
1221
  name=job_metadata,
@@ -11,7 +11,8 @@ logger = logging.getLogger(__name__)
11
11
 
12
12
 
13
13
  class FileFilter(BaseFilter):
14
- file_filter_regex = r'file:"(.+?)" ?'
14
+ file_filter_regex = r'(?<!-)file:"(.+?)" ?'
15
+ excluded_file_filter_regex = r'-file:"(.+?)" ?'
15
16
 
16
17
  def __init__(self, entry_key="file"):
17
18
  self.entry_key = entry_key
@@ -20,7 +21,9 @@ class FileFilter(BaseFilter):
20
21
 
21
22
  def get_filter_terms(self, query: str) -> List[str]:
22
23
  "Get all filter terms in query"
23
- return [f"{self.convert_to_regex(term)}" for term in re.findall(self.file_filter_regex, query)]
24
+ required_files = [f"{required_file}" for required_file in re.findall(self.file_filter_regex, query)]
25
+ excluded_files = [f"-{excluded_file}" for excluded_file in re.findall(self.excluded_file_filter_regex, query)]
26
+ return required_files + excluded_files
24
27
 
25
28
  def convert_to_regex(self, file_filter: str) -> str:
26
29
  "Convert file filter to regex"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: khoj
3
- Version: 1.20.3
3
+ Version: 1.20.5.dev10
4
4
  Summary: Your Second Brain
5
5
  Project-URL: Homepage, https://khoj.dev
6
6
  Project-URL: Documentation, https://docs.khoj.dev