khoj 1.16.1.dev15__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 (242) hide show
  1. khoj/__init__.py +0 -0
  2. khoj/app/README.md +94 -0
  3. khoj/app/__init__.py +0 -0
  4. khoj/app/asgi.py +16 -0
  5. khoj/app/settings.py +192 -0
  6. khoj/app/urls.py +25 -0
  7. khoj/configure.py +424 -0
  8. khoj/database/__init__.py +0 -0
  9. khoj/database/adapters/__init__.py +1234 -0
  10. khoj/database/admin.py +290 -0
  11. khoj/database/apps.py +6 -0
  12. khoj/database/management/__init__.py +0 -0
  13. khoj/database/management/commands/__init__.py +0 -0
  14. khoj/database/management/commands/change_generated_images_url.py +61 -0
  15. khoj/database/management/commands/convert_images_png_to_webp.py +99 -0
  16. khoj/database/migrations/0001_khojuser.py +98 -0
  17. khoj/database/migrations/0002_googleuser.py +32 -0
  18. khoj/database/migrations/0003_vector_extension.py +10 -0
  19. khoj/database/migrations/0004_content_types_and_more.py +181 -0
  20. khoj/database/migrations/0005_embeddings_corpus_id.py +19 -0
  21. khoj/database/migrations/0006_embeddingsdates.py +33 -0
  22. khoj/database/migrations/0007_add_conversation.py +27 -0
  23. khoj/database/migrations/0008_alter_conversation_conversation_log.py +17 -0
  24. khoj/database/migrations/0009_khojapiuser.py +24 -0
  25. khoj/database/migrations/0010_chatmodeloptions_and_more.py +83 -0
  26. khoj/database/migrations/0010_rename_embeddings_entry_and_more.py +30 -0
  27. khoj/database/migrations/0011_merge_20231102_0138.py +14 -0
  28. khoj/database/migrations/0012_entry_file_source.py +21 -0
  29. khoj/database/migrations/0013_subscription.py +37 -0
  30. khoj/database/migrations/0014_alter_googleuser_picture.py +17 -0
  31. khoj/database/migrations/0015_alter_subscription_user.py +21 -0
  32. khoj/database/migrations/0016_alter_subscription_renewal_date.py +17 -0
  33. khoj/database/migrations/0017_searchmodel.py +32 -0
  34. khoj/database/migrations/0018_searchmodelconfig_delete_searchmodel.py +30 -0
  35. khoj/database/migrations/0019_alter_googleuser_family_name_and_more.py +27 -0
  36. khoj/database/migrations/0020_reflectivequestion.py +36 -0
  37. khoj/database/migrations/0021_speechtotextmodeloptions_and_more.py +42 -0
  38. khoj/database/migrations/0022_texttoimagemodelconfig.py +25 -0
  39. khoj/database/migrations/0023_usersearchmodelconfig.py +33 -0
  40. khoj/database/migrations/0024_alter_entry_embeddings.py +18 -0
  41. khoj/database/migrations/0025_clientapplication_khojuser_phone_number_and_more.py +46 -0
  42. khoj/database/migrations/0025_searchmodelconfig_embeddings_inference_endpoint_and_more.py +22 -0
  43. khoj/database/migrations/0026_searchmodelconfig_cross_encoder_inference_endpoint_and_more.py +22 -0
  44. khoj/database/migrations/0027_merge_20240118_1324.py +13 -0
  45. khoj/database/migrations/0028_khojuser_verified_phone_number.py +17 -0
  46. khoj/database/migrations/0029_userrequests.py +27 -0
  47. khoj/database/migrations/0030_conversation_slug_and_title.py +38 -0
  48. khoj/database/migrations/0031_agent_conversation_agent.py +53 -0
  49. khoj/database/migrations/0031_alter_googleuser_locale.py +30 -0
  50. khoj/database/migrations/0032_merge_20240322_0427.py +14 -0
  51. khoj/database/migrations/0033_rename_tuning_agent_personality.py +17 -0
  52. khoj/database/migrations/0034_alter_chatmodeloptions_chat_model.py +32 -0
  53. khoj/database/migrations/0035_processlock.py +26 -0
  54. khoj/database/migrations/0036_alter_processlock_name.py +19 -0
  55. khoj/database/migrations/0036_delete_offlinechatprocessorconversationconfig.py +15 -0
  56. khoj/database/migrations/0036_publicconversation.py +42 -0
  57. khoj/database/migrations/0037_chatmodeloptions_openai_config_and_more.py +51 -0
  58. khoj/database/migrations/0037_searchmodelconfig_bi_encoder_docs_encode_config_and_more.py +32 -0
  59. khoj/database/migrations/0038_merge_20240425_0857.py +14 -0
  60. khoj/database/migrations/0038_merge_20240426_1640.py +12 -0
  61. khoj/database/migrations/0039_merge_20240501_0301.py +12 -0
  62. khoj/database/migrations/0040_alter_processlock_name.py +26 -0
  63. khoj/database/migrations/0040_merge_20240504_1010.py +14 -0
  64. khoj/database/migrations/0041_merge_20240505_1234.py +14 -0
  65. khoj/database/migrations/0042_serverchatsettings.py +46 -0
  66. khoj/database/migrations/0043_alter_chatmodeloptions_model_type.py +21 -0
  67. khoj/database/migrations/0044_conversation_file_filters.py +17 -0
  68. khoj/database/migrations/0045_fileobject.py +37 -0
  69. khoj/database/migrations/0046_khojuser_email_verification_code_and_more.py +22 -0
  70. khoj/database/migrations/0047_alter_entry_file_type.py +31 -0
  71. khoj/database/migrations/0048_voicemodeloption_uservoicemodelconfig.py +52 -0
  72. khoj/database/migrations/0049_datastore.py +38 -0
  73. khoj/database/migrations/0049_texttoimagemodelconfig_api_key_and_more.py +58 -0
  74. khoj/database/migrations/0050_alter_processlock_name.py +25 -0
  75. khoj/database/migrations/0051_merge_20240702_1220.py +14 -0
  76. khoj/database/migrations/0052_alter_searchmodelconfig_bi_encoder_docs_encode_config_and_more.py +27 -0
  77. khoj/database/migrations/__init__.py +0 -0
  78. khoj/database/models/__init__.py +402 -0
  79. khoj/database/tests.py +3 -0
  80. khoj/interface/email/feedback.html +34 -0
  81. khoj/interface/email/magic_link.html +17 -0
  82. khoj/interface/email/task.html +40 -0
  83. khoj/interface/email/welcome.html +61 -0
  84. khoj/interface/web/404.html +56 -0
  85. khoj/interface/web/agent.html +312 -0
  86. khoj/interface/web/agents.html +276 -0
  87. khoj/interface/web/assets/icons/agents.svg +6 -0
  88. khoj/interface/web/assets/icons/automation.svg +37 -0
  89. khoj/interface/web/assets/icons/cancel.svg +3 -0
  90. khoj/interface/web/assets/icons/chat.svg +24 -0
  91. khoj/interface/web/assets/icons/collapse.svg +17 -0
  92. khoj/interface/web/assets/icons/computer.png +0 -0
  93. khoj/interface/web/assets/icons/confirm-icon.svg +1 -0
  94. khoj/interface/web/assets/icons/copy-button-success.svg +6 -0
  95. khoj/interface/web/assets/icons/copy-button.svg +5 -0
  96. khoj/interface/web/assets/icons/credit-card.png +0 -0
  97. khoj/interface/web/assets/icons/delete.svg +26 -0
  98. khoj/interface/web/assets/icons/docx.svg +7 -0
  99. khoj/interface/web/assets/icons/edit.svg +4 -0
  100. khoj/interface/web/assets/icons/favicon-128x128.ico +0 -0
  101. khoj/interface/web/assets/icons/favicon-128x128.png +0 -0
  102. khoj/interface/web/assets/icons/favicon-256x256.png +0 -0
  103. khoj/interface/web/assets/icons/favicon.icns +0 -0
  104. khoj/interface/web/assets/icons/github.svg +1 -0
  105. khoj/interface/web/assets/icons/key.svg +4 -0
  106. khoj/interface/web/assets/icons/khoj-logo-sideways-200.png +0 -0
  107. khoj/interface/web/assets/icons/khoj-logo-sideways-500.png +0 -0
  108. khoj/interface/web/assets/icons/khoj-logo-sideways.svg +5385 -0
  109. khoj/interface/web/assets/icons/logotype.svg +1 -0
  110. khoj/interface/web/assets/icons/markdown.svg +1 -0
  111. khoj/interface/web/assets/icons/new.svg +23 -0
  112. khoj/interface/web/assets/icons/notion.svg +4 -0
  113. khoj/interface/web/assets/icons/openai-logomark.svg +1 -0
  114. khoj/interface/web/assets/icons/org.svg +1 -0
  115. khoj/interface/web/assets/icons/pdf.svg +23 -0
  116. khoj/interface/web/assets/icons/pencil-edit.svg +5 -0
  117. khoj/interface/web/assets/icons/plaintext.svg +1 -0
  118. khoj/interface/web/assets/icons/question-mark-icon.svg +1 -0
  119. khoj/interface/web/assets/icons/search.svg +25 -0
  120. khoj/interface/web/assets/icons/send.svg +1 -0
  121. khoj/interface/web/assets/icons/share.svg +8 -0
  122. khoj/interface/web/assets/icons/speaker.svg +4 -0
  123. khoj/interface/web/assets/icons/stop-solid.svg +37 -0
  124. khoj/interface/web/assets/icons/sync.svg +4 -0
  125. khoj/interface/web/assets/icons/thumbs-down-svgrepo-com.svg +6 -0
  126. khoj/interface/web/assets/icons/thumbs-up-svgrepo-com.svg +6 -0
  127. khoj/interface/web/assets/icons/user-silhouette.svg +4 -0
  128. khoj/interface/web/assets/icons/voice.svg +8 -0
  129. khoj/interface/web/assets/icons/web.svg +2 -0
  130. khoj/interface/web/assets/icons/whatsapp.svg +17 -0
  131. khoj/interface/web/assets/khoj.css +237 -0
  132. khoj/interface/web/assets/markdown-it.min.js +8476 -0
  133. khoj/interface/web/assets/natural-cron.min.js +1 -0
  134. khoj/interface/web/assets/org.min.js +1823 -0
  135. khoj/interface/web/assets/pico.min.css +5 -0
  136. khoj/interface/web/assets/purify.min.js +3 -0
  137. khoj/interface/web/assets/samples/desktop-browse-draw-sample.png +0 -0
  138. khoj/interface/web/assets/samples/desktop-plain-chat-sample.png +0 -0
  139. khoj/interface/web/assets/samples/desktop-remember-plan-sample.png +0 -0
  140. khoj/interface/web/assets/samples/phone-browse-draw-sample.png +0 -0
  141. khoj/interface/web/assets/samples/phone-plain-chat-sample.png +0 -0
  142. khoj/interface/web/assets/samples/phone-remember-plan-sample.png +0 -0
  143. khoj/interface/web/assets/utils.js +33 -0
  144. khoj/interface/web/base_config.html +445 -0
  145. khoj/interface/web/chat.html +3546 -0
  146. khoj/interface/web/config.html +1011 -0
  147. khoj/interface/web/config_automation.html +1103 -0
  148. khoj/interface/web/content_source_computer_input.html +139 -0
  149. khoj/interface/web/content_source_github_input.html +216 -0
  150. khoj/interface/web/content_source_notion_input.html +94 -0
  151. khoj/interface/web/khoj.webmanifest +51 -0
  152. khoj/interface/web/login.html +219 -0
  153. khoj/interface/web/public_conversation.html +2006 -0
  154. khoj/interface/web/search.html +470 -0
  155. khoj/interface/web/utils.html +48 -0
  156. khoj/main.py +241 -0
  157. khoj/manage.py +22 -0
  158. khoj/migrations/__init__.py +0 -0
  159. khoj/migrations/migrate_offline_chat_default_model.py +69 -0
  160. khoj/migrations/migrate_offline_chat_default_model_2.py +71 -0
  161. khoj/migrations/migrate_offline_chat_schema.py +83 -0
  162. khoj/migrations/migrate_offline_model.py +29 -0
  163. khoj/migrations/migrate_processor_config_openai.py +67 -0
  164. khoj/migrations/migrate_server_pg.py +138 -0
  165. khoj/migrations/migrate_version.py +17 -0
  166. khoj/processor/__init__.py +0 -0
  167. khoj/processor/content/__init__.py +0 -0
  168. khoj/processor/content/docx/__init__.py +0 -0
  169. khoj/processor/content/docx/docx_to_entries.py +110 -0
  170. khoj/processor/content/github/__init__.py +0 -0
  171. khoj/processor/content/github/github_to_entries.py +224 -0
  172. khoj/processor/content/images/__init__.py +0 -0
  173. khoj/processor/content/images/image_to_entries.py +118 -0
  174. khoj/processor/content/markdown/__init__.py +0 -0
  175. khoj/processor/content/markdown/markdown_to_entries.py +165 -0
  176. khoj/processor/content/notion/notion_to_entries.py +260 -0
  177. khoj/processor/content/org_mode/__init__.py +0 -0
  178. khoj/processor/content/org_mode/org_to_entries.py +231 -0
  179. khoj/processor/content/org_mode/orgnode.py +532 -0
  180. khoj/processor/content/pdf/__init__.py +0 -0
  181. khoj/processor/content/pdf/pdf_to_entries.py +116 -0
  182. khoj/processor/content/plaintext/__init__.py +0 -0
  183. khoj/processor/content/plaintext/plaintext_to_entries.py +122 -0
  184. khoj/processor/content/text_to_entries.py +297 -0
  185. khoj/processor/conversation/__init__.py +0 -0
  186. khoj/processor/conversation/anthropic/__init__.py +0 -0
  187. khoj/processor/conversation/anthropic/anthropic_chat.py +206 -0
  188. khoj/processor/conversation/anthropic/utils.py +114 -0
  189. khoj/processor/conversation/offline/__init__.py +0 -0
  190. khoj/processor/conversation/offline/chat_model.py +231 -0
  191. khoj/processor/conversation/offline/utils.py +78 -0
  192. khoj/processor/conversation/offline/whisper.py +15 -0
  193. khoj/processor/conversation/openai/__init__.py +0 -0
  194. khoj/processor/conversation/openai/gpt.py +187 -0
  195. khoj/processor/conversation/openai/utils.py +129 -0
  196. khoj/processor/conversation/openai/whisper.py +13 -0
  197. khoj/processor/conversation/prompts.py +758 -0
  198. khoj/processor/conversation/utils.py +262 -0
  199. khoj/processor/embeddings.py +117 -0
  200. khoj/processor/speech/__init__.py +0 -0
  201. khoj/processor/speech/text_to_speech.py +51 -0
  202. khoj/processor/tools/__init__.py +0 -0
  203. khoj/processor/tools/online_search.py +225 -0
  204. khoj/routers/__init__.py +0 -0
  205. khoj/routers/api.py +626 -0
  206. khoj/routers/api_agents.py +43 -0
  207. khoj/routers/api_chat.py +1180 -0
  208. khoj/routers/api_config.py +434 -0
  209. khoj/routers/api_phone.py +86 -0
  210. khoj/routers/auth.py +181 -0
  211. khoj/routers/email.py +133 -0
  212. khoj/routers/helpers.py +1188 -0
  213. khoj/routers/indexer.py +349 -0
  214. khoj/routers/notion.py +91 -0
  215. khoj/routers/storage.py +35 -0
  216. khoj/routers/subscription.py +104 -0
  217. khoj/routers/twilio.py +36 -0
  218. khoj/routers/web_client.py +471 -0
  219. khoj/search_filter/__init__.py +0 -0
  220. khoj/search_filter/base_filter.py +15 -0
  221. khoj/search_filter/date_filter.py +217 -0
  222. khoj/search_filter/file_filter.py +30 -0
  223. khoj/search_filter/word_filter.py +29 -0
  224. khoj/search_type/__init__.py +0 -0
  225. khoj/search_type/text_search.py +241 -0
  226. khoj/utils/__init__.py +0 -0
  227. khoj/utils/cli.py +93 -0
  228. khoj/utils/config.py +81 -0
  229. khoj/utils/constants.py +24 -0
  230. khoj/utils/fs_syncer.py +249 -0
  231. khoj/utils/helpers.py +418 -0
  232. khoj/utils/initialization.py +146 -0
  233. khoj/utils/jsonl.py +43 -0
  234. khoj/utils/models.py +47 -0
  235. khoj/utils/rawconfig.py +160 -0
  236. khoj/utils/state.py +46 -0
  237. khoj/utils/yaml.py +43 -0
  238. khoj-1.16.1.dev15.dist-info/METADATA +178 -0
  239. khoj-1.16.1.dev15.dist-info/RECORD +242 -0
  240. khoj-1.16.1.dev15.dist-info/WHEEL +4 -0
  241. khoj-1.16.1.dev15.dist-info/entry_points.txt +2 -0
  242. khoj-1.16.1.dev15.dist-info/licenses/LICENSE +661 -0
khoj/utils/config.py ADDED
@@ -0,0 +1,81 @@
1
+ # System Packages
2
+ from __future__ import annotations # to avoid quoting type hints
3
+
4
+ import logging
5
+ from dataclasses import dataclass
6
+ from enum import Enum
7
+ from typing import TYPE_CHECKING, Any, List, Optional, Union
8
+
9
+ import torch
10
+
11
+ from khoj.processor.conversation.offline.utils import download_model
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ if TYPE_CHECKING:
17
+ from sentence_transformers import CrossEncoder
18
+
19
+ from khoj.utils.models import BaseEncoder
20
+
21
+
22
+ class SearchType(str, Enum):
23
+ All = "all"
24
+ Org = "org"
25
+ Markdown = "markdown"
26
+ Image = "image"
27
+ Pdf = "pdf"
28
+ Github = "github"
29
+ Notion = "notion"
30
+ Plaintext = "plaintext"
31
+ Docx = "docx"
32
+
33
+
34
+ class ProcessorType(str, Enum):
35
+ Conversation = "conversation"
36
+
37
+
38
+ @dataclass
39
+ class TextContent:
40
+ enabled: bool
41
+
42
+
43
+ @dataclass
44
+ class ImageContent:
45
+ image_names: List[str]
46
+ image_embeddings: torch.Tensor
47
+ image_metadata_embeddings: torch.Tensor
48
+
49
+
50
+ @dataclass
51
+ class TextSearchModel:
52
+ bi_encoder: BaseEncoder
53
+ cross_encoder: Optional[CrossEncoder] = None
54
+ top_k: Optional[int] = 15
55
+
56
+
57
+ @dataclass
58
+ class ImageSearchModel:
59
+ image_encoder: BaseEncoder
60
+
61
+
62
+ @dataclass
63
+ class SearchModels:
64
+ text_search: Optional[TextSearchModel] = None
65
+
66
+
67
+ @dataclass
68
+ class OfflineChatProcessorConfig:
69
+ loaded_model: Union[Any, None] = None
70
+
71
+
72
+ class OfflineChatProcessorModel:
73
+ def __init__(self, chat_model: str = "NousResearch/Hermes-2-Pro-Mistral-7B-GGUF", max_tokens: int = None):
74
+ self.chat_model = chat_model
75
+ self.loaded_model = None
76
+ try:
77
+ self.loaded_model = download_model(self.chat_model, max_tokens=max_tokens)
78
+ except ValueError as e:
79
+ self.loaded_model = None
80
+ logger.error(f"Error while loading offline chat model: {e}", exc_info=True)
81
+ raise e
@@ -0,0 +1,24 @@
1
+ from pathlib import Path
2
+
3
+ app_root_directory = Path(__file__).parent.parent.parent
4
+ web_directory = app_root_directory / "khoj/interface/web/"
5
+ next_js_directory = app_root_directory / "khoj/interface/built/"
6
+ empty_escape_sequences = "\n|\r|\t| "
7
+ app_env_filepath = "~/.khoj/env"
8
+ telemetry_server = "https://khoj.beta.haletic.com/v1/telemetry"
9
+ content_directory = "~/.khoj/content/"
10
+ default_offline_chat_model = "NousResearch/Hermes-2-Pro-Mistral-7B-GGUF"
11
+ default_online_chat_model = "gpt-4-turbo-preview"
12
+
13
+ empty_config = {
14
+ "search-type": {
15
+ "image": {"encoder": "sentence-transformers/clip-ViT-B-32", "model_directory": "~/.khoj/search/image/"},
16
+ },
17
+ }
18
+
19
+ # default app config to use
20
+ default_config = {
21
+ "search-type": {
22
+ "image": {"encoder": "sentence-transformers/clip-ViT-B-32", "model_directory": "~/.khoj/search/image/"},
23
+ },
24
+ }
@@ -0,0 +1,249 @@
1
+ import glob
2
+ import logging
3
+ import os
4
+ from pathlib import Path
5
+ from typing import Optional
6
+
7
+ from bs4 import BeautifulSoup
8
+ from magika import Magika
9
+
10
+ from khoj.database.models import (
11
+ LocalMarkdownConfig,
12
+ LocalOrgConfig,
13
+ LocalPdfConfig,
14
+ LocalPlaintextConfig,
15
+ )
16
+ from khoj.utils.config import SearchType
17
+ from khoj.utils.helpers import get_absolute_path, is_none_or_empty
18
+ from khoj.utils.rawconfig import TextContentConfig
19
+
20
+ logger = logging.getLogger(__name__)
21
+ magika = Magika()
22
+
23
+
24
+ def collect_files(search_type: Optional[SearchType] = SearchType.All, user=None) -> dict:
25
+ files = {}
26
+
27
+ if search_type == SearchType.All or search_type == SearchType.Org:
28
+ org_config = LocalOrgConfig.objects.filter(user=user).first()
29
+ files["org"] = get_org_files(construct_config_from_db(org_config)) if org_config else {}
30
+ if search_type == SearchType.All or search_type == SearchType.Markdown:
31
+ markdown_config = LocalMarkdownConfig.objects.filter(user=user).first()
32
+ files["markdown"] = get_markdown_files(construct_config_from_db(markdown_config)) if markdown_config else {}
33
+ if search_type == SearchType.All or search_type == SearchType.Plaintext:
34
+ plaintext_config = LocalPlaintextConfig.objects.filter(user=user).first()
35
+ files["plaintext"] = get_plaintext_files(construct_config_from_db(plaintext_config)) if plaintext_config else {}
36
+ if search_type == SearchType.All or search_type == SearchType.Pdf:
37
+ pdf_config = LocalPdfConfig.objects.filter(user=user).first()
38
+ files["pdf"] = get_pdf_files(construct_config_from_db(pdf_config)) if pdf_config else {}
39
+ return files
40
+
41
+
42
+ def construct_config_from_db(db_config) -> TextContentConfig:
43
+ return TextContentConfig(
44
+ input_files=db_config.input_files,
45
+ input_filter=db_config.input_filter,
46
+ index_heading_entries=db_config.index_heading_entries,
47
+ )
48
+
49
+
50
+ def get_plaintext_files(config: TextContentConfig) -> dict[str, str]:
51
+ def is_plaintextfile(file: str):
52
+ "Check if file is plaintext file"
53
+ # Check if file path exists
54
+ content_group = magika.identify_path(Path(file)).output.group
55
+ # Use file extension to decide plaintext if file content is not identifiable
56
+ valid_text_file_extensions = ("txt", "md", "markdown", "org" "mbox", "rst", "html", "htm", "xml")
57
+ return file.endswith(valid_text_file_extensions) or content_group in ["text", "code"]
58
+
59
+ def extract_html_content(html_content: str):
60
+ "Extract content from HTML"
61
+ soup = BeautifulSoup(html_content, "html.parser")
62
+ return soup.get_text(strip=True, separator="\n")
63
+
64
+ # Extract required fields from config
65
+ input_files, input_filters = (
66
+ config.input_files,
67
+ config.input_filter,
68
+ )
69
+
70
+ # Input Validation
71
+ if is_none_or_empty(input_files) and is_none_or_empty(input_filters):
72
+ logger.debug("At least one of input-files or input-file-filter is required to be specified")
73
+ return {}
74
+
75
+ # Get all plain text files to process
76
+ absolute_plaintext_files, filtered_plaintext_files = set(), set()
77
+ if input_files:
78
+ absolute_plaintext_files = {get_absolute_path(jsonl_file) for jsonl_file in input_files}
79
+ if input_filters:
80
+ filtered_plaintext_files = {
81
+ filtered_file
82
+ for plaintext_file_filter in input_filters
83
+ for filtered_file in glob.glob(get_absolute_path(plaintext_file_filter), recursive=True)
84
+ if os.path.isfile(filtered_file)
85
+ }
86
+
87
+ all_target_files = sorted(absolute_plaintext_files | filtered_plaintext_files)
88
+
89
+ files_with_no_plaintext_extensions = {
90
+ target_files for target_files in all_target_files if not is_plaintextfile(target_files)
91
+ }
92
+ if any(files_with_no_plaintext_extensions):
93
+ logger.warning(f"Skipping unsupported files from plaintext indexing: {files_with_no_plaintext_extensions}")
94
+ all_target_files = list(set(all_target_files) - files_with_no_plaintext_extensions)
95
+
96
+ logger.debug(f"Processing files: {all_target_files}")
97
+
98
+ filename_to_content_map = {}
99
+ for file in all_target_files:
100
+ with open(file, "r", encoding="utf8") as f:
101
+ try:
102
+ plaintext_content = f.read()
103
+ if file.endswith(("html", "htm", "xml")):
104
+ plaintext_content = extract_html_content(plaintext_content)
105
+ filename_to_content_map[file] = plaintext_content
106
+ except Exception as e:
107
+ logger.warning(f"Unable to read file: {file} as plaintext. Skipping file.")
108
+ logger.warning(e, exc_info=True)
109
+
110
+ return filename_to_content_map
111
+
112
+
113
+ def get_org_files(config: TextContentConfig):
114
+ # Extract required fields from config
115
+ org_files, org_file_filters = (
116
+ config.input_files,
117
+ config.input_filter,
118
+ )
119
+
120
+ # Input Validation
121
+ if is_none_or_empty(org_files) and is_none_or_empty(org_file_filters):
122
+ logger.debug("At least one of org-files or org-file-filter is required to be specified")
123
+ return {}
124
+
125
+ "Get Org files to process"
126
+ absolute_org_files, filtered_org_files = set(), set()
127
+ if org_files:
128
+ absolute_org_files = {get_absolute_path(org_file) for org_file in org_files}
129
+ if org_file_filters:
130
+ filtered_org_files = {
131
+ filtered_file
132
+ for org_file_filter in org_file_filters
133
+ for filtered_file in glob.glob(get_absolute_path(org_file_filter), recursive=True)
134
+ if os.path.isfile(filtered_file)
135
+ }
136
+
137
+ all_org_files = sorted(absolute_org_files | filtered_org_files)
138
+
139
+ files_with_non_org_extensions = {org_file for org_file in all_org_files if not org_file.endswith(".org")}
140
+ if any(files_with_non_org_extensions):
141
+ logger.warning(f"There maybe non org-mode files in the input set: {files_with_non_org_extensions}")
142
+
143
+ logger.debug(f"Processing files: {all_org_files}")
144
+
145
+ filename_to_content_map = {}
146
+ for file in all_org_files:
147
+ with open(file, "r", encoding="utf8") as f:
148
+ try:
149
+ filename_to_content_map[file] = f.read()
150
+ except Exception as e:
151
+ logger.warning(f"Unable to read file: {file} as org. Skipping file.")
152
+ logger.warning(e, exc_info=True)
153
+
154
+ return filename_to_content_map
155
+
156
+
157
+ def get_markdown_files(config: TextContentConfig):
158
+ # Extract required fields from config
159
+ markdown_files, markdown_file_filters = (
160
+ config.input_files,
161
+ config.input_filter,
162
+ )
163
+
164
+ # Input Validation
165
+ if is_none_or_empty(markdown_files) and is_none_or_empty(markdown_file_filters):
166
+ logger.debug("At least one of markdown-files or markdown-file-filter is required to be specified")
167
+ return {}
168
+
169
+ # Get markdown files to process
170
+ absolute_markdown_files, filtered_markdown_files = set(), set()
171
+ if markdown_files:
172
+ absolute_markdown_files = {get_absolute_path(markdown_file) for markdown_file in markdown_files}
173
+
174
+ if markdown_file_filters:
175
+ filtered_markdown_files = {
176
+ filtered_file
177
+ for markdown_file_filter in markdown_file_filters
178
+ for filtered_file in glob.glob(get_absolute_path(markdown_file_filter), recursive=True)
179
+ if os.path.isfile(filtered_file)
180
+ }
181
+
182
+ all_markdown_files = sorted(absolute_markdown_files | filtered_markdown_files)
183
+
184
+ files_with_non_markdown_extensions = {
185
+ md_file for md_file in all_markdown_files if not md_file.endswith(".md") and not md_file.endswith(".markdown")
186
+ }
187
+
188
+ if any(files_with_non_markdown_extensions):
189
+ logger.warning(
190
+ f"[Warning] There maybe non markdown-mode files in the input set: {files_with_non_markdown_extensions}"
191
+ )
192
+
193
+ logger.debug(f"Processing files: {all_markdown_files}")
194
+
195
+ filename_to_content_map = {}
196
+ for file in all_markdown_files:
197
+ with open(file, "r", encoding="utf8") as f:
198
+ try:
199
+ filename_to_content_map[file] = f.read()
200
+ except Exception as e:
201
+ logger.warning(f"Unable to read file: {file} as markdown. Skipping file.")
202
+ logger.warning(e, exc_info=True)
203
+
204
+ return filename_to_content_map
205
+
206
+
207
+ def get_pdf_files(config: TextContentConfig):
208
+ # Extract required fields from config
209
+ pdf_files, pdf_file_filters = (
210
+ config.input_files,
211
+ config.input_filter,
212
+ )
213
+
214
+ # Input Validation
215
+ if is_none_or_empty(pdf_files) and is_none_or_empty(pdf_file_filters):
216
+ logger.debug("At least one of pdf-files or pdf-file-filter is required to be specified")
217
+ return {}
218
+
219
+ # Get PDF files to process
220
+ absolute_pdf_files, filtered_pdf_files = set(), set()
221
+ if pdf_files:
222
+ absolute_pdf_files = {get_absolute_path(pdf_file) for pdf_file in pdf_files}
223
+ if pdf_file_filters:
224
+ filtered_pdf_files = {
225
+ filtered_file
226
+ for pdf_file_filter in pdf_file_filters
227
+ for filtered_file in glob.glob(get_absolute_path(pdf_file_filter), recursive=True)
228
+ if os.path.isfile(filtered_file)
229
+ }
230
+
231
+ all_pdf_files = sorted(absolute_pdf_files | filtered_pdf_files)
232
+
233
+ files_with_non_pdf_extensions = {pdf_file for pdf_file in all_pdf_files if not pdf_file.endswith(".pdf")}
234
+
235
+ if any(files_with_non_pdf_extensions):
236
+ logger.warning(f"[Warning] There maybe non pdf-mode files in the input set: {files_with_non_pdf_extensions}")
237
+
238
+ logger.debug(f"Processing files: {all_pdf_files}")
239
+
240
+ filename_to_content_map = {}
241
+ for file in all_pdf_files:
242
+ with open(file, "rb") as f:
243
+ try:
244
+ filename_to_content_map[file] = f.read()
245
+ except Exception as e:
246
+ logger.warning(f"Unable to read file: {file} as PDF. Skipping file.")
247
+ logger.warning(e, exc_info=True)
248
+
249
+ return filename_to_content_map