khoj 1.30.11.dev15__py3-none-any.whl → 1.30.11.dev46__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 (55) hide show
  1. khoj/database/admin.py +3 -10
  2. khoj/database/migrations/0075_migrate_generated_assets_and_validate.py +85 -0
  3. khoj/database/models/__init__.py +164 -31
  4. khoj/interface/compiled/404/index.html +1 -1
  5. khoj/interface/compiled/_next/static/chunks/1603-f5babe72ba9f6a59.js +1 -0
  6. khoj/interface/compiled/_next/static/chunks/5538-0ea2d3944ca051e1.js +1 -0
  7. khoj/interface/compiled/_next/static/chunks/app/agents/{page-2ffa7560aebff9a1.js → page-f5c0801b27a8e95e.js} +1 -1
  8. khoj/interface/compiled/_next/static/chunks/app/automations/{page-b0a6a6ed2267c1a2.js → page-8691f6c09a0acd44.js} +1 -1
  9. khoj/interface/compiled/_next/static/chunks/app/chat/{page-02f8616bba3e449e.js → page-135d56dd4263e40d.js} +1 -1
  10. khoj/interface/compiled/_next/static/chunks/app/{page-3ffd8f0934b896f3.js → page-e79ace822d51557b.js} +1 -1
  11. khoj/interface/compiled/_next/static/chunks/app/search/layout-cae84c87073877f0.js +1 -0
  12. khoj/interface/compiled/_next/static/chunks/app/search/{page-059f237514f77628.js → page-e8b578d155550386.js} +1 -1
  13. khoj/interface/compiled/_next/static/chunks/app/settings/{page-32e9423bede5b4a1.js → page-b6c835050c970be7.js} +1 -1
  14. khoj/interface/compiled/_next/static/chunks/app/share/chat/layout-6f4879fbbf8b90f7.js +1 -0
  15. khoj/interface/compiled/_next/static/chunks/app/share/chat/{page-0b8d90dc57dbc1d8.js → page-635635e4fb39fe29.js} +1 -1
  16. khoj/interface/compiled/_next/static/chunks/{webpack-062298330010d2aa.js → webpack-5203c3872078c10c.js} +1 -1
  17. khoj/interface/compiled/_next/static/css/592ca99f5122e75a.css +1 -0
  18. khoj/interface/compiled/_next/static/css/edd3abaf11580924.css +1 -0
  19. khoj/interface/compiled/agents/index.html +1 -1
  20. khoj/interface/compiled/agents/index.txt +2 -2
  21. khoj/interface/compiled/assets/icons/khoj_lantern.svg +100 -0
  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/index.html +1 -1
  27. khoj/interface/compiled/index.txt +2 -2
  28. khoj/interface/compiled/search/index.html +1 -1
  29. khoj/interface/compiled/search/index.txt +2 -2
  30. khoj/interface/compiled/settings/index.html +1 -1
  31. khoj/interface/compiled/settings/index.txt +2 -2
  32. khoj/interface/compiled/share/chat/index.html +1 -1
  33. khoj/interface/compiled/share/chat/index.txt +2 -2
  34. khoj/processor/conversation/anthropic/anthropic_chat.py +11 -3
  35. khoj/processor/conversation/google/gemini_chat.py +11 -3
  36. khoj/processor/conversation/offline/chat_model.py +6 -2
  37. khoj/processor/conversation/openai/gpt.py +10 -2
  38. khoj/processor/conversation/prompts.py +18 -0
  39. khoj/processor/conversation/utils.py +82 -26
  40. khoj/processor/image/generate.py +10 -13
  41. khoj/routers/api_chat.py +49 -98
  42. khoj/routers/helpers.py +41 -1
  43. {khoj-1.30.11.dev15.dist-info → khoj-1.30.11.dev46.dist-info}/METADATA +1 -1
  44. {khoj-1.30.11.dev15.dist-info → khoj-1.30.11.dev46.dist-info}/RECORD +49 -47
  45. khoj/interface/compiled/_next/static/chunks/1603-c68d44bc4ae6039a.js +0 -1
  46. khoj/interface/compiled/_next/static/chunks/5538-e5f3c9f4d67a64b9.js +0 -1
  47. khoj/interface/compiled/_next/static/chunks/app/search/layout-2ca475462c0b2176.js +0 -1
  48. khoj/interface/compiled/_next/static/chunks/app/share/chat/layout-592e8c470f2c2084.js +0 -1
  49. khoj/interface/compiled/_next/static/css/80bd6301fc657983.css +0 -1
  50. khoj/interface/compiled/_next/static/css/9d45de78fba367c1.css +0 -1
  51. /khoj/interface/compiled/_next/static/{HwvgU9Hbpk8cDtZdz8u6Z → VhbBwTudxbu82AwZhVKwF}/_buildManifest.js +0 -0
  52. /khoj/interface/compiled/_next/static/{HwvgU9Hbpk8cDtZdz8u6Z → VhbBwTudxbu82AwZhVKwF}/_ssgManifest.js +0 -0
  53. {khoj-1.30.11.dev15.dist-info → khoj-1.30.11.dev46.dist-info}/WHEEL +0 -0
  54. {khoj-1.30.11.dev15.dist-info → khoj-1.30.11.dev46.dist-info}/entry_points.txt +0 -0
  55. {khoj-1.30.11.dev15.dist-info → khoj-1.30.11.dev46.dist-info}/licenses/LICENSE +0 -0
@@ -154,7 +154,7 @@ def construct_chat_history(conversation_history: dict, n: int = 4, agent_name="A
154
154
  chat_history += f'{agent_name}: {{"queries": {chat["intent"].get("inferred-queries")}}}\n'
155
155
 
156
156
  chat_history += f"{agent_name}: {chat['message']}\n\n"
157
- elif chat["by"] == "khoj" and ("text-to-image" in chat["intent"].get("type")):
157
+ elif chat["by"] == "khoj" and chat.get("images"):
158
158
  chat_history += f"User: {chat['intent']['query']}\n"
159
159
  chat_history += f"{agent_name}: [generated image redacted for space]\n"
160
160
  elif chat["by"] == "khoj" and ("excalidraw" in chat["intent"].get("type")):
@@ -213,6 +213,7 @@ class ChatEvent(Enum):
213
213
  END_LLM_RESPONSE = "end_llm_response"
214
214
  MESSAGE = "message"
215
215
  REFERENCES = "references"
216
+ GENERATED_ASSETS = "generated_assets"
216
217
  STATUS = "status"
217
218
  METADATA = "metadata"
218
219
  USAGE = "usage"
@@ -225,7 +226,6 @@ def message_to_log(
225
226
  user_message_metadata={},
226
227
  khoj_message_metadata={},
227
228
  conversation_log=[],
228
- train_of_thought=[],
229
229
  ):
230
230
  """Create json logs from messages, metadata for conversation log"""
231
231
  default_khoj_message_metadata = {
@@ -234,6 +234,10 @@ def message_to_log(
234
234
  }
235
235
  khoj_response_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
236
236
 
237
+ # Filter out any fields that are set to None
238
+ user_message_metadata = {k: v for k, v in user_message_metadata.items() if v is not None}
239
+ khoj_message_metadata = {k: v for k, v in khoj_message_metadata.items() if v is not None}
240
+
237
241
  # Create json log from Human's message
238
242
  human_log = merge_dicts({"message": user_message, "by": "you"}, user_message_metadata)
239
243
 
@@ -261,31 +265,41 @@ def save_to_conversation_log(
261
265
  automation_id: str = None,
262
266
  query_images: List[str] = None,
263
267
  raw_query_files: List[FileAttachment] = [],
268
+ generated_images: List[str] = [],
269
+ raw_generated_files: List[FileAttachment] = [],
270
+ generated_excalidraw_diagram: str = None,
264
271
  train_of_thought: List[Any] = [],
265
272
  tracer: Dict[str, Any] = {},
266
273
  ):
267
274
  user_message_time = user_message_time or datetime.now().strftime("%Y-%m-%d %H:%M:%S")
268
275
  turn_id = tracer.get("mid") or str(uuid.uuid4())
276
+
277
+ user_message_metadata = {"created": user_message_time, "images": query_images, "turnId": turn_id}
278
+
279
+ if raw_query_files and len(raw_query_files) > 0:
280
+ user_message_metadata["queryFiles"] = [file.model_dump(mode="json") for file in raw_query_files]
281
+
282
+ khoj_message_metadata = {
283
+ "context": compiled_references,
284
+ "intent": {"inferred-queries": inferred_queries, "type": intent_type},
285
+ "onlineContext": online_results,
286
+ "codeContext": code_results,
287
+ "automationId": automation_id,
288
+ "trainOfThought": train_of_thought,
289
+ "turnId": turn_id,
290
+ "images": generated_images,
291
+ "queryFiles": [file.model_dump(mode="json") for file in raw_generated_files],
292
+ }
293
+
294
+ if generated_excalidraw_diagram:
295
+ khoj_message_metadata["excalidrawDiagram"] = generated_excalidraw_diagram
296
+
269
297
  updated_conversation = message_to_log(
270
298
  user_message=q,
271
299
  chat_response=chat_response,
272
- user_message_metadata={
273
- "created": user_message_time,
274
- "images": query_images,
275
- "turnId": turn_id,
276
- "queryFiles": [file.model_dump(mode="json") for file in raw_query_files],
277
- },
278
- khoj_message_metadata={
279
- "context": compiled_references,
280
- "intent": {"inferred-queries": inferred_queries, "type": intent_type},
281
- "onlineContext": online_results,
282
- "codeContext": code_results,
283
- "automationId": automation_id,
284
- "trainOfThought": train_of_thought,
285
- "turnId": turn_id,
286
- },
300
+ user_message_metadata=user_message_metadata,
301
+ khoj_message_metadata=khoj_message_metadata,
287
302
  conversation_log=meta_log.get("chat", []),
288
- train_of_thought=train_of_thought,
289
303
  )
290
304
  ConversationAdapters.save_conversation(
291
305
  user,
@@ -303,13 +317,13 @@ def save_to_conversation_log(
303
317
  Saved Conversation Turn
304
318
  You ({user.username}): "{q}"
305
319
 
306
- Khoj: "{inferred_queries if ("text-to-image" in intent_type) else chat_response}"
320
+ Khoj: "{chat_response}"
307
321
  """.strip()
308
322
  )
309
323
 
310
324
 
311
325
  def construct_structured_message(
312
- message: str, images: list[str], model_type: str, vision_enabled: bool, attached_file_context: str
326
+ message: str, images: list[str], model_type: str, vision_enabled: bool, attached_file_context: str = None
313
327
  ):
314
328
  """
315
329
  Format messages into appropriate multimedia format for supported chat model types
@@ -327,7 +341,8 @@ def construct_structured_message(
327
341
  constructed_messages.append({"type": "text", "text": attached_file_context})
328
342
  if vision_enabled and images:
329
343
  for image in images:
330
- constructed_messages.append({"type": "image_url", "image_url": {"url": image}})
344
+ if image.startswith("https://"):
345
+ constructed_messages.append({"type": "image_url", "image_url": {"url": image}})
331
346
  return constructed_messages
332
347
 
333
348
  if not is_none_or_empty(attached_file_context):
@@ -365,6 +380,10 @@ def generate_chatml_messages_with_context(
365
380
  model_type="",
366
381
  context_message="",
367
382
  query_files: str = None,
383
+ generated_images: Optional[list[str]] = None,
384
+ generated_files: List[FileAttachment] = None,
385
+ generated_excalidraw_diagram: str = None,
386
+ program_execution_context: List[str] = [],
368
387
  ):
369
388
  """Generate chat messages with appropriate context from previous conversation to send to the chat model"""
370
389
  # Set max prompt size from user config or based on pre-configured for model and machine specs
@@ -384,6 +403,7 @@ def generate_chatml_messages_with_context(
384
403
  message_attached_files = ""
385
404
 
386
405
  chat_message = chat.get("message")
406
+ role = "user" if chat["by"] == "you" else "assistant"
387
407
 
388
408
  if chat["by"] == "khoj" and "excalidraw" in chat["intent"].get("type", ""):
389
409
  chat_message = chat["intent"].get("inferred-queries")[0]
@@ -404,7 +424,7 @@ def generate_chatml_messages_with_context(
404
424
  query_files_dict[file["name"]] = file["content"]
405
425
 
406
426
  message_attached_files = gather_raw_query_files(query_files_dict)
407
- chatml_messages.append(ChatMessage(content=message_attached_files, role="user"))
427
+ chatml_messages.append(ChatMessage(content=message_attached_files, role=role))
408
428
 
409
429
  if not is_none_or_empty(chat.get("onlineContext")):
410
430
  message_context += f"{prompts.online_search_conversation.format(online_results=chat.get('onlineContext'))}"
@@ -413,10 +433,20 @@ def generate_chatml_messages_with_context(
413
433
  reconstructed_context_message = ChatMessage(content=message_context, role="user")
414
434
  chatml_messages.insert(0, reconstructed_context_message)
415
435
 
416
- role = "user" if chat["by"] == "you" else "assistant"
417
- message_content = construct_structured_message(
418
- chat_message, chat.get("images"), model_type, vision_enabled, attached_file_context=query_files
419
- )
436
+ if chat.get("images"):
437
+ if role == "assistant":
438
+ # Issue: the assistant role cannot accept an image as a message content, so send it in a separate user message.
439
+ file_attachment_message = construct_structured_message(
440
+ message=prompts.generated_image_attachment.format(),
441
+ images=chat.get("images"),
442
+ model_type=model_type,
443
+ vision_enabled=vision_enabled,
444
+ )
445
+ chatml_messages.append(ChatMessage(content=file_attachment_message, role="user"))
446
+ else:
447
+ message_content = construct_structured_message(
448
+ chat_message, chat.get("images"), model_type, vision_enabled
449
+ )
420
450
 
421
451
  reconstructed_message = ChatMessage(content=message_content, role=role)
422
452
  chatml_messages.insert(0, reconstructed_message)
@@ -425,6 +455,7 @@ def generate_chatml_messages_with_context(
425
455
  break
426
456
 
427
457
  messages = []
458
+
428
459
  if not is_none_or_empty(user_message):
429
460
  messages.append(
430
461
  ChatMessage(
@@ -437,6 +468,31 @@ def generate_chatml_messages_with_context(
437
468
  if not is_none_or_empty(context_message):
438
469
  messages.append(ChatMessage(content=context_message, role="user"))
439
470
 
471
+ if generated_images:
472
+ messages.append(
473
+ ChatMessage(
474
+ content=construct_structured_message(
475
+ prompts.generated_image_attachment.format(), generated_images, model_type, vision_enabled
476
+ ),
477
+ role="user",
478
+ )
479
+ )
480
+
481
+ if generated_files:
482
+ message_attached_files = gather_raw_query_files({file.name: file.content for file in generated_files})
483
+ messages.append(ChatMessage(content=message_attached_files, role="assistant"))
484
+
485
+ if generated_excalidraw_diagram:
486
+ messages.append(ChatMessage(content=prompts.generated_diagram_attachment.format(), role="assistant"))
487
+
488
+ if program_execution_context:
489
+ messages.append(
490
+ ChatMessage(
491
+ content=prompts.additional_program_context.format(context="\n".join(program_execution_context)),
492
+ role="assistant",
493
+ )
494
+ )
495
+
440
496
  if len(chatml_messages) > 0:
441
497
  messages += chatml_messages
442
498
 
@@ -12,7 +12,7 @@ from khoj.database.models import Agent, KhojUser, TextToImageModelConfig
12
12
  from khoj.routers.helpers import ChatEvent, generate_better_image_prompt
13
13
  from khoj.routers.storage import upload_image
14
14
  from khoj.utils import state
15
- from khoj.utils.helpers import ImageIntentType, convert_image_to_webp, timer
15
+ from khoj.utils.helpers import convert_image_to_webp, timer
16
16
  from khoj.utils.rawconfig import LocationData
17
17
 
18
18
  logger = logging.getLogger(__name__)
@@ -34,14 +34,13 @@ async def text_to_image(
34
34
  status_code = 200
35
35
  image = None
36
36
  image_url = None
37
- intent_type = ImageIntentType.TEXT_TO_IMAGE_V3
38
37
 
39
38
  text_to_image_config = await ConversationAdapters.aget_user_text_to_image_model(user)
40
39
  if not text_to_image_config:
41
40
  # If the user has not configured a text to image model, return an unsupported on server error
42
41
  status_code = 501
43
42
  message = "Failed to generate image. Setup image generation on the server."
44
- yield image_url or image, status_code, message, intent_type.value
43
+ yield image_url or image, status_code, message
45
44
  return
46
45
 
47
46
  text2image_model = text_to_image_config.model_name
@@ -50,8 +49,8 @@ async def text_to_image(
50
49
  if chat["by"] == "khoj" and chat["intent"].get("type") in ["remember", "reminder"]:
51
50
  chat_history += f"Q: {chat['intent']['query']}\n"
52
51
  chat_history += f"A: {chat['message']}\n"
53
- elif chat["by"] == "khoj" and "text-to-image" in chat["intent"].get("type"):
54
- chat_history += f"Q: Prompt: {chat['intent']['query']}\n"
52
+ elif chat["by"] == "khoj" and chat.get("images"):
53
+ chat_history += f"Q: {chat['intent']['query']}\n"
55
54
  chat_history += f"A: Improved Prompt: {chat['intent']['inferred-queries'][0]}\n"
56
55
 
57
56
  if send_status_func:
@@ -92,31 +91,29 @@ async def text_to_image(
92
91
  logger.error(f"Image Generation blocked by OpenAI: {e}")
93
92
  status_code = e.status_code # type: ignore
94
93
  message = f"Image generation blocked by OpenAI due to policy violation" # type: ignore
95
- yield image_url or image, status_code, message, intent_type.value
94
+ yield image_url or image, status_code, message
96
95
  return
97
96
  else:
98
97
  logger.error(f"Image Generation failed with {e}", exc_info=True)
99
98
  message = f"Image generation failed using OpenAI" # type: ignore
100
99
  status_code = e.status_code # type: ignore
101
- yield image_url or image, status_code, message, intent_type.value
100
+ yield image_url or image, status_code, message
102
101
  return
103
102
  except requests.RequestException as e:
104
103
  logger.error(f"Image Generation failed with {e}", exc_info=True)
105
104
  message = f"Image generation using {text2image_model} via {text_to_image_config.model_type} failed due to a network error."
106
105
  status_code = 502
107
- yield image_url or image, status_code, message, intent_type.value
106
+ yield image_url or image, status_code, message
108
107
  return
109
108
 
110
109
  # Decide how to store the generated image
111
110
  with timer("Upload image to S3", logger):
112
111
  image_url = upload_image(webp_image_bytes, user.uuid)
113
- if image_url:
114
- intent_type = ImageIntentType.TEXT_TO_IMAGE2
115
- else:
116
- intent_type = ImageIntentType.TEXT_TO_IMAGE_V3
112
+
113
+ if not image_url:
117
114
  image = base64.b64encode(webp_image_bytes).decode("utf-8")
118
115
 
119
- yield image_url or image, status_code, image_prompt, intent_type.value
116
+ yield image_url or image, status_code, image_prompt
120
117
 
121
118
 
122
119
  def generate_image_with_openai(
khoj/routers/api_chat.py CHANGED
@@ -77,6 +77,7 @@ from khoj.utils.helpers import (
77
77
  )
78
78
  from khoj.utils.rawconfig import (
79
79
  ChatRequestBody,
80
+ FileAttachment,
80
81
  FileFilterRequest,
81
82
  FilesFilterRequest,
82
83
  LocationData,
@@ -770,6 +771,11 @@ async def chat(
770
771
  file_filters = conversation.file_filters if conversation and conversation.file_filters else []
771
772
  attached_file_context = gather_raw_query_files(query_files)
772
773
 
774
+ generated_images: List[str] = []
775
+ generated_files: List[FileAttachment] = []
776
+ generated_excalidraw_diagram: str = None
777
+ program_execution_context: List[str] = []
778
+
773
779
  if conversation_commands == [ConversationCommand.Default] or is_automated_task:
774
780
  chosen_io = await aget_data_sources_and_output_format(
775
781
  q,
@@ -875,21 +881,17 @@ async def chat(
875
881
  async for result in send_llm_response(response, tracer.get("usage")):
876
882
  yield result
877
883
 
878
- await sync_to_async(save_to_conversation_log)(
879
- q,
880
- response_log,
881
- user,
882
- meta_log,
883
- user_message_time,
884
- intent_type="summarize",
885
- client_application=request.user.client_app,
886
- conversation_id=conversation_id,
887
- query_images=uploaded_images,
888
- train_of_thought=train_of_thought,
889
- raw_query_files=raw_query_files,
890
- tracer=tracer,
884
+ summarized_document = FileAttachment(
885
+ name="Summarized Document",
886
+ content=response_log,
887
+ type="text/plain",
888
+ size=len(response_log.encode("utf-8")),
891
889
  )
892
- return
890
+
891
+ async for result in send_event(ChatEvent.GENERATED_ASSETS, {"files": [summarized_document.model_dump()]}):
892
+ yield result
893
+
894
+ generated_files.append(summarized_document)
893
895
 
894
896
  custom_filters = []
895
897
  if conversation_commands == [ConversationCommand.Help]:
@@ -1078,6 +1080,7 @@ async def chat(
1078
1080
  async for result in send_event(ChatEvent.STATUS, f"**Ran code snippets**: {len(code_results)}"):
1079
1081
  yield result
1080
1082
  except ValueError as e:
1083
+ program_execution_context.append(f"Failed to run code")
1081
1084
  logger.warning(
1082
1085
  f"Failed to use code tool: {e}. Attempting to respond without code results",
1083
1086
  exc_info=True,
@@ -1115,51 +1118,28 @@ async def chat(
1115
1118
  if isinstance(result, dict) and ChatEvent.STATUS in result:
1116
1119
  yield result[ChatEvent.STATUS]
1117
1120
  else:
1118
- generated_image, status_code, improved_image_prompt, intent_type = result
1121
+ generated_image, status_code, improved_image_prompt = result
1119
1122
 
1123
+ inferred_queries.append(improved_image_prompt)
1120
1124
  if generated_image is None or status_code != 200:
1121
- content_obj = {
1122
- "content-type": "application/json",
1123
- "intentType": intent_type,
1124
- "detail": improved_image_prompt,
1125
- "image": None,
1126
- }
1127
- async for result in send_llm_response(json.dumps(content_obj), tracer.get("usage")):
1125
+ program_execution_context.append(f"Failed to generate image with {improved_image_prompt}")
1126
+ async for result in send_event(ChatEvent.STATUS, f"Failed to generate image"):
1128
1127
  yield result
1129
- return
1128
+ else:
1129
+ generated_images.append(generated_image)
1130
1130
 
1131
- await sync_to_async(save_to_conversation_log)(
1132
- q,
1133
- generated_image,
1134
- user,
1135
- meta_log,
1136
- user_message_time,
1137
- intent_type=intent_type,
1138
- inferred_queries=[improved_image_prompt],
1139
- client_application=request.user.client_app,
1140
- conversation_id=conversation_id,
1141
- compiled_references=compiled_references,
1142
- online_results=online_results,
1143
- code_results=code_results,
1144
- query_images=uploaded_images,
1145
- train_of_thought=train_of_thought,
1146
- raw_query_files=raw_query_files,
1147
- tracer=tracer,
1148
- )
1149
- content_obj = {
1150
- "intentType": intent_type,
1151
- "inferredQueries": [improved_image_prompt],
1152
- "image": generated_image,
1153
- }
1154
- async for result in send_llm_response(json.dumps(content_obj), tracer.get("usage")):
1155
- yield result
1156
- return
1131
+ async for result in send_event(
1132
+ ChatEvent.GENERATED_ASSETS,
1133
+ {
1134
+ "images": [generated_image],
1135
+ },
1136
+ ):
1137
+ yield result
1157
1138
 
1158
1139
  if ConversationCommand.Diagram in conversation_commands:
1159
1140
  async for result in send_event(ChatEvent.STATUS, f"Creating diagram"):
1160
1141
  yield result
1161
1142
 
1162
- intent_type = "excalidraw"
1163
1143
  inferred_queries = []
1164
1144
  diagram_description = ""
1165
1145
 
@@ -1183,62 +1163,29 @@ async def chat(
1183
1163
  if better_diagram_description_prompt and excalidraw_diagram_description:
1184
1164
  inferred_queries.append(better_diagram_description_prompt)
1185
1165
  diagram_description = excalidraw_diagram_description
1166
+
1167
+ generated_excalidraw_diagram = diagram_description
1168
+
1169
+ async for result in send_event(
1170
+ ChatEvent.GENERATED_ASSETS,
1171
+ {
1172
+ "excalidrawDiagram": excalidraw_diagram_description,
1173
+ },
1174
+ ):
1175
+ yield result
1186
1176
  else:
1187
1177
  error_message = "Failed to generate diagram. Please try again later."
1188
- async for result in send_llm_response(error_message, tracer.get("usage")):
1189
- yield result
1190
-
1191
- await sync_to_async(save_to_conversation_log)(
1192
- q,
1193
- error_message,
1194
- user,
1195
- meta_log,
1196
- user_message_time,
1197
- inferred_queries=[better_diagram_description_prompt],
1198
- client_application=request.user.client_app,
1199
- conversation_id=conversation_id,
1200
- compiled_references=compiled_references,
1201
- online_results=online_results,
1202
- code_results=code_results,
1203
- query_images=uploaded_images,
1204
- train_of_thought=train_of_thought,
1205
- raw_query_files=raw_query_files,
1206
- tracer=tracer,
1178
+ program_execution_context.append(
1179
+ f"AI attempted to programmatically generate a diagram but failed due to a program issue. Generally, it is able to do so, but encountered a system issue this time. AI can suggest text description or rendering of the diagram or user can try again with a simpler prompt."
1207
1180
  )
1208
- return
1209
-
1210
- content_obj = {
1211
- "intentType": intent_type,
1212
- "inferredQueries": inferred_queries,
1213
- "image": diagram_description,
1214
- }
1215
1181
 
1216
- await sync_to_async(save_to_conversation_log)(
1217
- q,
1218
- excalidraw_diagram_description,
1219
- user,
1220
- meta_log,
1221
- user_message_time,
1222
- intent_type="excalidraw",
1223
- inferred_queries=[better_diagram_description_prompt],
1224
- client_application=request.user.client_app,
1225
- conversation_id=conversation_id,
1226
- compiled_references=compiled_references,
1227
- online_results=online_results,
1228
- code_results=code_results,
1229
- query_images=uploaded_images,
1230
- train_of_thought=train_of_thought,
1231
- raw_query_files=raw_query_files,
1232
- tracer=tracer,
1233
- )
1234
-
1235
- async for result in send_llm_response(json.dumps(content_obj), tracer.get("usage")):
1236
- yield result
1237
- return
1182
+ async for result in send_event(ChatEvent.STATUS, error_message):
1183
+ yield result
1238
1184
 
1239
1185
  ## Generate Text Output
1240
1186
  async for result in send_event(ChatEvent.STATUS, f"**Generating a well-informed response**"):
1241
1187
  yield result
1188
+
1242
1189
  llm_response, chat_metadata = await agenerate_chat_response(
1243
1190
  defiltered_query,
1244
1191
  meta_log,
@@ -1258,6 +1205,10 @@ async def chat(
1258
1205
  train_of_thought,
1259
1206
  attached_file_context,
1260
1207
  raw_query_files,
1208
+ generated_images,
1209
+ generated_files,
1210
+ generated_excalidraw_diagram,
1211
+ program_execution_context,
1261
1212
  tracer,
1262
1213
  )
1263
1214
 
khoj/routers/helpers.py CHANGED
@@ -1185,6 +1185,10 @@ def generate_chat_response(
1185
1185
  train_of_thought: List[Any] = [],
1186
1186
  query_files: str = None,
1187
1187
  raw_query_files: List[FileAttachment] = None,
1188
+ generated_images: List[str] = None,
1189
+ raw_generated_files: List[FileAttachment] = [],
1190
+ generated_excalidraw_diagram: str = None,
1191
+ program_execution_context: List[str] = [],
1188
1192
  tracer: dict = {},
1189
1193
  ) -> Tuple[Union[ThreadedGenerator, Iterator[str]], Dict[str, str]]:
1190
1194
  # Initialize Variables
@@ -1208,6 +1212,9 @@ def generate_chat_response(
1208
1212
  query_images=query_images,
1209
1213
  train_of_thought=train_of_thought,
1210
1214
  raw_query_files=raw_query_files,
1215
+ generated_images=generated_images,
1216
+ raw_generated_files=raw_generated_files,
1217
+ generated_excalidraw_diagram=generated_excalidraw_diagram,
1211
1218
  tracer=tracer,
1212
1219
  )
1213
1220
 
@@ -1243,6 +1250,7 @@ def generate_chat_response(
1243
1250
  user_name=user_name,
1244
1251
  agent=agent,
1245
1252
  query_files=query_files,
1253
+ generated_files=raw_generated_files,
1246
1254
  tracer=tracer,
1247
1255
  )
1248
1256
 
@@ -1269,6 +1277,10 @@ def generate_chat_response(
1269
1277
  agent=agent,
1270
1278
  vision_available=vision_available,
1271
1279
  query_files=query_files,
1280
+ generated_files=raw_generated_files,
1281
+ generated_images=generated_images,
1282
+ generated_excalidraw_diagram=generated_excalidraw_diagram,
1283
+ program_execution_context=program_execution_context,
1272
1284
  tracer=tracer,
1273
1285
  )
1274
1286
 
@@ -1292,6 +1304,10 @@ def generate_chat_response(
1292
1304
  agent=agent,
1293
1305
  vision_available=vision_available,
1294
1306
  query_files=query_files,
1307
+ generated_files=raw_generated_files,
1308
+ generated_images=generated_images,
1309
+ generated_excalidraw_diagram=generated_excalidraw_diagram,
1310
+ program_execution_context=program_execution_context,
1295
1311
  tracer=tracer,
1296
1312
  )
1297
1313
  elif conversation_config.model_type == ChatModelOptions.ModelType.GOOGLE:
@@ -1314,6 +1330,10 @@ def generate_chat_response(
1314
1330
  query_images=query_images,
1315
1331
  vision_available=vision_available,
1316
1332
  query_files=query_files,
1333
+ generated_files=raw_generated_files,
1334
+ generated_images=generated_images,
1335
+ generated_excalidraw_diagram=generated_excalidraw_diagram,
1336
+ program_execution_context=program_execution_context,
1317
1337
  tracer=tracer,
1318
1338
  )
1319
1339
 
@@ -1785,6 +1805,9 @@ class MessageProcessor:
1785
1805
  self.references = {}
1786
1806
  self.usage = {}
1787
1807
  self.raw_response = ""
1808
+ self.generated_images = []
1809
+ self.generated_files = []
1810
+ self.generated_excalidraw_diagram = []
1788
1811
 
1789
1812
  def convert_message_chunk_to_json(self, raw_chunk: str) -> Dict[str, Any]:
1790
1813
  if raw_chunk.startswith("{") and raw_chunk.endswith("}"):
@@ -1823,6 +1846,16 @@ class MessageProcessor:
1823
1846
  self.raw_response += chunk_data
1824
1847
  else:
1825
1848
  self.raw_response += chunk_data
1849
+ elif chunk_type == ChatEvent.GENERATED_ASSETS:
1850
+ chunk_data = chunk["data"]
1851
+ if isinstance(chunk_data, dict):
1852
+ for key in chunk_data:
1853
+ if key == "images":
1854
+ self.generated_images = chunk_data[key]
1855
+ elif key == "files":
1856
+ self.generated_files = chunk_data[key]
1857
+ elif key == "excalidrawDiagram":
1858
+ self.generated_excalidraw_diagram = chunk_data[key]
1826
1859
 
1827
1860
  def handle_json_response(self, json_data: Dict[str, str]) -> str | Dict[str, str]:
1828
1861
  if "image" in json_data or "details" in json_data:
@@ -1853,7 +1886,14 @@ async def read_chat_stream(response_iterator: AsyncGenerator[str, None]) -> Dict
1853
1886
  if buffer:
1854
1887
  processor.process_message_chunk(buffer)
1855
1888
 
1856
- return {"response": processor.raw_response, "references": processor.references, "usage": processor.usage}
1889
+ return {
1890
+ "response": processor.raw_response,
1891
+ "references": processor.references,
1892
+ "usage": processor.usage,
1893
+ "images": processor.generated_images,
1894
+ "files": processor.generated_files,
1895
+ "excalidrawDiagram": processor.generated_excalidraw_diagram,
1896
+ }
1857
1897
 
1858
1898
 
1859
1899
  def get_user_config(user: KhojUser, request: Request, is_detailed: bool = False):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: khoj
3
- Version: 1.30.11.dev15
3
+ Version: 1.30.11.dev46
4
4
  Summary: Your Second Brain
5
5
  Project-URL: Homepage, https://khoj.dev
6
6
  Project-URL: Documentation, https://docs.khoj.dev