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.
- khoj/__init__.py +0 -0
- khoj/app/README.md +94 -0
- khoj/app/__init__.py +0 -0
- khoj/app/asgi.py +16 -0
- khoj/app/settings.py +192 -0
- khoj/app/urls.py +25 -0
- khoj/configure.py +424 -0
- khoj/database/__init__.py +0 -0
- khoj/database/adapters/__init__.py +1234 -0
- khoj/database/admin.py +290 -0
- khoj/database/apps.py +6 -0
- khoj/database/management/__init__.py +0 -0
- khoj/database/management/commands/__init__.py +0 -0
- khoj/database/management/commands/change_generated_images_url.py +61 -0
- khoj/database/management/commands/convert_images_png_to_webp.py +99 -0
- khoj/database/migrations/0001_khojuser.py +98 -0
- khoj/database/migrations/0002_googleuser.py +32 -0
- khoj/database/migrations/0003_vector_extension.py +10 -0
- khoj/database/migrations/0004_content_types_and_more.py +181 -0
- khoj/database/migrations/0005_embeddings_corpus_id.py +19 -0
- khoj/database/migrations/0006_embeddingsdates.py +33 -0
- khoj/database/migrations/0007_add_conversation.py +27 -0
- khoj/database/migrations/0008_alter_conversation_conversation_log.py +17 -0
- khoj/database/migrations/0009_khojapiuser.py +24 -0
- khoj/database/migrations/0010_chatmodeloptions_and_more.py +83 -0
- khoj/database/migrations/0010_rename_embeddings_entry_and_more.py +30 -0
- khoj/database/migrations/0011_merge_20231102_0138.py +14 -0
- khoj/database/migrations/0012_entry_file_source.py +21 -0
- khoj/database/migrations/0013_subscription.py +37 -0
- khoj/database/migrations/0014_alter_googleuser_picture.py +17 -0
- khoj/database/migrations/0015_alter_subscription_user.py +21 -0
- khoj/database/migrations/0016_alter_subscription_renewal_date.py +17 -0
- khoj/database/migrations/0017_searchmodel.py +32 -0
- khoj/database/migrations/0018_searchmodelconfig_delete_searchmodel.py +30 -0
- khoj/database/migrations/0019_alter_googleuser_family_name_and_more.py +27 -0
- khoj/database/migrations/0020_reflectivequestion.py +36 -0
- khoj/database/migrations/0021_speechtotextmodeloptions_and_more.py +42 -0
- khoj/database/migrations/0022_texttoimagemodelconfig.py +25 -0
- khoj/database/migrations/0023_usersearchmodelconfig.py +33 -0
- khoj/database/migrations/0024_alter_entry_embeddings.py +18 -0
- khoj/database/migrations/0025_clientapplication_khojuser_phone_number_and_more.py +46 -0
- khoj/database/migrations/0025_searchmodelconfig_embeddings_inference_endpoint_and_more.py +22 -0
- khoj/database/migrations/0026_searchmodelconfig_cross_encoder_inference_endpoint_and_more.py +22 -0
- khoj/database/migrations/0027_merge_20240118_1324.py +13 -0
- khoj/database/migrations/0028_khojuser_verified_phone_number.py +17 -0
- khoj/database/migrations/0029_userrequests.py +27 -0
- khoj/database/migrations/0030_conversation_slug_and_title.py +38 -0
- khoj/database/migrations/0031_agent_conversation_agent.py +53 -0
- khoj/database/migrations/0031_alter_googleuser_locale.py +30 -0
- khoj/database/migrations/0032_merge_20240322_0427.py +14 -0
- khoj/database/migrations/0033_rename_tuning_agent_personality.py +17 -0
- khoj/database/migrations/0034_alter_chatmodeloptions_chat_model.py +32 -0
- khoj/database/migrations/0035_processlock.py +26 -0
- khoj/database/migrations/0036_alter_processlock_name.py +19 -0
- khoj/database/migrations/0036_delete_offlinechatprocessorconversationconfig.py +15 -0
- khoj/database/migrations/0036_publicconversation.py +42 -0
- khoj/database/migrations/0037_chatmodeloptions_openai_config_and_more.py +51 -0
- khoj/database/migrations/0037_searchmodelconfig_bi_encoder_docs_encode_config_and_more.py +32 -0
- khoj/database/migrations/0038_merge_20240425_0857.py +14 -0
- khoj/database/migrations/0038_merge_20240426_1640.py +12 -0
- khoj/database/migrations/0039_merge_20240501_0301.py +12 -0
- khoj/database/migrations/0040_alter_processlock_name.py +26 -0
- khoj/database/migrations/0040_merge_20240504_1010.py +14 -0
- khoj/database/migrations/0041_merge_20240505_1234.py +14 -0
- khoj/database/migrations/0042_serverchatsettings.py +46 -0
- khoj/database/migrations/0043_alter_chatmodeloptions_model_type.py +21 -0
- khoj/database/migrations/0044_conversation_file_filters.py +17 -0
- khoj/database/migrations/0045_fileobject.py +37 -0
- khoj/database/migrations/0046_khojuser_email_verification_code_and_more.py +22 -0
- khoj/database/migrations/0047_alter_entry_file_type.py +31 -0
- khoj/database/migrations/0048_voicemodeloption_uservoicemodelconfig.py +52 -0
- khoj/database/migrations/0049_datastore.py +38 -0
- khoj/database/migrations/0049_texttoimagemodelconfig_api_key_and_more.py +58 -0
- khoj/database/migrations/0050_alter_processlock_name.py +25 -0
- khoj/database/migrations/0051_merge_20240702_1220.py +14 -0
- khoj/database/migrations/0052_alter_searchmodelconfig_bi_encoder_docs_encode_config_and_more.py +27 -0
- khoj/database/migrations/__init__.py +0 -0
- khoj/database/models/__init__.py +402 -0
- khoj/database/tests.py +3 -0
- khoj/interface/email/feedback.html +34 -0
- khoj/interface/email/magic_link.html +17 -0
- khoj/interface/email/task.html +40 -0
- khoj/interface/email/welcome.html +61 -0
- khoj/interface/web/404.html +56 -0
- khoj/interface/web/agent.html +312 -0
- khoj/interface/web/agents.html +276 -0
- khoj/interface/web/assets/icons/agents.svg +6 -0
- khoj/interface/web/assets/icons/automation.svg +37 -0
- khoj/interface/web/assets/icons/cancel.svg +3 -0
- khoj/interface/web/assets/icons/chat.svg +24 -0
- khoj/interface/web/assets/icons/collapse.svg +17 -0
- khoj/interface/web/assets/icons/computer.png +0 -0
- khoj/interface/web/assets/icons/confirm-icon.svg +1 -0
- khoj/interface/web/assets/icons/copy-button-success.svg +6 -0
- khoj/interface/web/assets/icons/copy-button.svg +5 -0
- khoj/interface/web/assets/icons/credit-card.png +0 -0
- khoj/interface/web/assets/icons/delete.svg +26 -0
- khoj/interface/web/assets/icons/docx.svg +7 -0
- khoj/interface/web/assets/icons/edit.svg +4 -0
- khoj/interface/web/assets/icons/favicon-128x128.ico +0 -0
- khoj/interface/web/assets/icons/favicon-128x128.png +0 -0
- khoj/interface/web/assets/icons/favicon-256x256.png +0 -0
- khoj/interface/web/assets/icons/favicon.icns +0 -0
- khoj/interface/web/assets/icons/github.svg +1 -0
- khoj/interface/web/assets/icons/key.svg +4 -0
- khoj/interface/web/assets/icons/khoj-logo-sideways-200.png +0 -0
- khoj/interface/web/assets/icons/khoj-logo-sideways-500.png +0 -0
- khoj/interface/web/assets/icons/khoj-logo-sideways.svg +5385 -0
- khoj/interface/web/assets/icons/logotype.svg +1 -0
- khoj/interface/web/assets/icons/markdown.svg +1 -0
- khoj/interface/web/assets/icons/new.svg +23 -0
- khoj/interface/web/assets/icons/notion.svg +4 -0
- khoj/interface/web/assets/icons/openai-logomark.svg +1 -0
- khoj/interface/web/assets/icons/org.svg +1 -0
- khoj/interface/web/assets/icons/pdf.svg +23 -0
- khoj/interface/web/assets/icons/pencil-edit.svg +5 -0
- khoj/interface/web/assets/icons/plaintext.svg +1 -0
- khoj/interface/web/assets/icons/question-mark-icon.svg +1 -0
- khoj/interface/web/assets/icons/search.svg +25 -0
- khoj/interface/web/assets/icons/send.svg +1 -0
- khoj/interface/web/assets/icons/share.svg +8 -0
- khoj/interface/web/assets/icons/speaker.svg +4 -0
- khoj/interface/web/assets/icons/stop-solid.svg +37 -0
- khoj/interface/web/assets/icons/sync.svg +4 -0
- khoj/interface/web/assets/icons/thumbs-down-svgrepo-com.svg +6 -0
- khoj/interface/web/assets/icons/thumbs-up-svgrepo-com.svg +6 -0
- khoj/interface/web/assets/icons/user-silhouette.svg +4 -0
- khoj/interface/web/assets/icons/voice.svg +8 -0
- khoj/interface/web/assets/icons/web.svg +2 -0
- khoj/interface/web/assets/icons/whatsapp.svg +17 -0
- khoj/interface/web/assets/khoj.css +237 -0
- khoj/interface/web/assets/markdown-it.min.js +8476 -0
- khoj/interface/web/assets/natural-cron.min.js +1 -0
- khoj/interface/web/assets/org.min.js +1823 -0
- khoj/interface/web/assets/pico.min.css +5 -0
- khoj/interface/web/assets/purify.min.js +3 -0
- khoj/interface/web/assets/samples/desktop-browse-draw-sample.png +0 -0
- khoj/interface/web/assets/samples/desktop-plain-chat-sample.png +0 -0
- khoj/interface/web/assets/samples/desktop-remember-plan-sample.png +0 -0
- khoj/interface/web/assets/samples/phone-browse-draw-sample.png +0 -0
- khoj/interface/web/assets/samples/phone-plain-chat-sample.png +0 -0
- khoj/interface/web/assets/samples/phone-remember-plan-sample.png +0 -0
- khoj/interface/web/assets/utils.js +33 -0
- khoj/interface/web/base_config.html +445 -0
- khoj/interface/web/chat.html +3546 -0
- khoj/interface/web/config.html +1011 -0
- khoj/interface/web/config_automation.html +1103 -0
- khoj/interface/web/content_source_computer_input.html +139 -0
- khoj/interface/web/content_source_github_input.html +216 -0
- khoj/interface/web/content_source_notion_input.html +94 -0
- khoj/interface/web/khoj.webmanifest +51 -0
- khoj/interface/web/login.html +219 -0
- khoj/interface/web/public_conversation.html +2006 -0
- khoj/interface/web/search.html +470 -0
- khoj/interface/web/utils.html +48 -0
- khoj/main.py +241 -0
- khoj/manage.py +22 -0
- khoj/migrations/__init__.py +0 -0
- khoj/migrations/migrate_offline_chat_default_model.py +69 -0
- khoj/migrations/migrate_offline_chat_default_model_2.py +71 -0
- khoj/migrations/migrate_offline_chat_schema.py +83 -0
- khoj/migrations/migrate_offline_model.py +29 -0
- khoj/migrations/migrate_processor_config_openai.py +67 -0
- khoj/migrations/migrate_server_pg.py +138 -0
- khoj/migrations/migrate_version.py +17 -0
- khoj/processor/__init__.py +0 -0
- khoj/processor/content/__init__.py +0 -0
- khoj/processor/content/docx/__init__.py +0 -0
- khoj/processor/content/docx/docx_to_entries.py +110 -0
- khoj/processor/content/github/__init__.py +0 -0
- khoj/processor/content/github/github_to_entries.py +224 -0
- khoj/processor/content/images/__init__.py +0 -0
- khoj/processor/content/images/image_to_entries.py +118 -0
- khoj/processor/content/markdown/__init__.py +0 -0
- khoj/processor/content/markdown/markdown_to_entries.py +165 -0
- khoj/processor/content/notion/notion_to_entries.py +260 -0
- khoj/processor/content/org_mode/__init__.py +0 -0
- khoj/processor/content/org_mode/org_to_entries.py +231 -0
- khoj/processor/content/org_mode/orgnode.py +532 -0
- khoj/processor/content/pdf/__init__.py +0 -0
- khoj/processor/content/pdf/pdf_to_entries.py +116 -0
- khoj/processor/content/plaintext/__init__.py +0 -0
- khoj/processor/content/plaintext/plaintext_to_entries.py +122 -0
- khoj/processor/content/text_to_entries.py +297 -0
- khoj/processor/conversation/__init__.py +0 -0
- khoj/processor/conversation/anthropic/__init__.py +0 -0
- khoj/processor/conversation/anthropic/anthropic_chat.py +206 -0
- khoj/processor/conversation/anthropic/utils.py +114 -0
- khoj/processor/conversation/offline/__init__.py +0 -0
- khoj/processor/conversation/offline/chat_model.py +231 -0
- khoj/processor/conversation/offline/utils.py +78 -0
- khoj/processor/conversation/offline/whisper.py +15 -0
- khoj/processor/conversation/openai/__init__.py +0 -0
- khoj/processor/conversation/openai/gpt.py +187 -0
- khoj/processor/conversation/openai/utils.py +129 -0
- khoj/processor/conversation/openai/whisper.py +13 -0
- khoj/processor/conversation/prompts.py +758 -0
- khoj/processor/conversation/utils.py +262 -0
- khoj/processor/embeddings.py +117 -0
- khoj/processor/speech/__init__.py +0 -0
- khoj/processor/speech/text_to_speech.py +51 -0
- khoj/processor/tools/__init__.py +0 -0
- khoj/processor/tools/online_search.py +225 -0
- khoj/routers/__init__.py +0 -0
- khoj/routers/api.py +626 -0
- khoj/routers/api_agents.py +43 -0
- khoj/routers/api_chat.py +1180 -0
- khoj/routers/api_config.py +434 -0
- khoj/routers/api_phone.py +86 -0
- khoj/routers/auth.py +181 -0
- khoj/routers/email.py +133 -0
- khoj/routers/helpers.py +1188 -0
- khoj/routers/indexer.py +349 -0
- khoj/routers/notion.py +91 -0
- khoj/routers/storage.py +35 -0
- khoj/routers/subscription.py +104 -0
- khoj/routers/twilio.py +36 -0
- khoj/routers/web_client.py +471 -0
- khoj/search_filter/__init__.py +0 -0
- khoj/search_filter/base_filter.py +15 -0
- khoj/search_filter/date_filter.py +217 -0
- khoj/search_filter/file_filter.py +30 -0
- khoj/search_filter/word_filter.py +29 -0
- khoj/search_type/__init__.py +0 -0
- khoj/search_type/text_search.py +241 -0
- khoj/utils/__init__.py +0 -0
- khoj/utils/cli.py +93 -0
- khoj/utils/config.py +81 -0
- khoj/utils/constants.py +24 -0
- khoj/utils/fs_syncer.py +249 -0
- khoj/utils/helpers.py +418 -0
- khoj/utils/initialization.py +146 -0
- khoj/utils/jsonl.py +43 -0
- khoj/utils/models.py +47 -0
- khoj/utils/rawconfig.py +160 -0
- khoj/utils/state.py +46 -0
- khoj/utils/yaml.py +43 -0
- khoj-1.16.1.dev15.dist-info/METADATA +178 -0
- khoj-1.16.1.dev15.dist-info/RECORD +242 -0
- khoj-1.16.1.dev15.dist-info/WHEEL +4 -0
- khoj-1.16.1.dev15.dist-info/entry_points.txt +2 -0
- 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
|
khoj/utils/constants.py
ADDED
|
@@ -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
|
+
}
|
khoj/utils/fs_syncer.py
ADDED
|
@@ -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
|