+ );
+}
+
+export default UploadPhotoButton;
\ No newline at end of file
diff --git a/Frontend/src/renderer/src/pages/Upload/UploadPhotoInput/UploadPhotoInput.jsx b/Frontend/src/renderer/src/pages/Upload/UploadPhotoInput/UploadPhotoInput.jsx
new file mode 100644
index 0000000..7914bba
--- /dev/null
+++ b/Frontend/src/renderer/src/pages/Upload/UploadPhotoInput/UploadPhotoInput.jsx
@@ -0,0 +1,30 @@
+const UploadPhotoInput = ({uploadPhotoInputRef, takenPhotos, setTakenPhotos, setCurrentIndex}) => {
+
+ const handlePhotoInput = (e) => {
+ const selectedFiles = Array.from(e.target.files);
+
+ // prevents user from uploading more than 3 photos
+ if (selectedFiles.length > 3) {
+ alert('You can only add a maximum of 3 files');
+ return true;
+ }
+
+ // clears takenPhotos array when reselecting photos
+ if (takenPhotos.length > 0) {
+ setTakenPhotos([]);
+ }
+
+ // sets all uploaded photos to the takenPhotos array
+ selectedFiles.forEach((photo) => {
+ const photoUrl = URL.createObjectURL(photo)
+ setTakenPhotos(prevPhotos => [...prevPhotos, photoUrl]);
+ });
+ setCurrentIndex(0);
+ }
+
+ return (
+
+ );
+}
+
+export default UploadPhotoInput;
\ No newline at end of file
diff --git a/ML Models/ModelForGenerateNotes.py b/ML Models/ModelForGenerateNotes.py
new file mode 100644
index 0000000..18dcdf1
--- /dev/null
+++ b/ML Models/ModelForGenerateNotes.py
@@ -0,0 +1,147 @@
+from paddleocr import PaddleOCR, draw_ocr
+import cv2
+import skimage.morphology as morph
+import os
+import numpy as np
+from model2 import OCRProcessor
+import base64
+
+notesImgPath = r"C:\Users\prern\OneDrive\Documents\GitHub\CS4250\InkWave\InkWave\models\cvModel\test2.jpg"
+notesImgPath2 = r"C:\Users\prern\OneDrive\Documents\GitHub\CS4250\InkWave\InkWave\ML Models\Test Images\PrernaNotesNoNums.jpg"
+
+def cv_model(img_path):
+ # Verify image path
+ if not os.path.exists(img_path):
+ raise FileNotFoundError(f"Image not found: {img_path}")
+
+ # Initialize your custom OCR processor
+ processor = OCRProcessor(paddle_language='en')
+
+ # Use it to process the image and extract text
+ extracted_text = processor.process_image(img_path)
+
+ if not extracted_text.strip():
+ raise ValueError("No text found in image.")
+
+ # Save OCR output to file
+ output_file = './cv_output.txt'
+ with open(output_file, 'w', encoding='utf-8') as file:
+ file.write(extracted_text)
+
+ return output_file
+
+
+import os
+from openai import OpenAI
+from dotenv import load_dotenv, find_dotenv
+
+'''''
+def llm_model(img_path, document_path):
+ """
+ Sends an image and text file to the GPT-4.1-mini model for correction and formatting.
+ """
+ load_dotenv(find_dotenv())
+ client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
+ model = "gpt-4.1-2025-04-14" # or whatever you're calling 4.1 mini — make sure this matches OpenAI's format
+ temperature = 0.7
+
+ # Load document text
+ try:
+ with open(document_path, 'r', encoding='utf-8') as file:
+ input_file = file.read()
+ except FileNotFoundError:
+ raise FileNotFoundError("Document file not found.")
+
+ # Upload the image file
+ try:
+ with open(img_path, "rb") as image_file:
+ uploaded_file = client.files.create(file=image_file, purpose="assistants") # 'assistants' is correct
+ file_id = uploaded_file.id
+ except Exception as e:
+ raise RuntimeError(f"Image upload failed: {e}")
+
+ # Build messages with file attachment
+ messages = [
+ {
+ "role": "system",
+ "content": "Correct the grammar and spelling in this note. Do not add new content."
+ },
+ {
+ "role": "user",
+ "content": [
+ {"type": "text", "text": input_file},
+ {"type": "file", "file_id": file_id}
+ ]
+ }
+ ]
+
+ # Send to LLM
+ response = client.chat.completions.create(
+ model=model,
+ messages=messages,
+ temperature=temperature
+ )
+
+ content = response.choices[0].message.content
+
+ # Save to file
+ output_file = "llm_output.md"
+ with open(output_file, "w", encoding='utf-8') as file:
+ file.write(content)
+
+ return output_file
+
+'''''
+load_dotenv(find_dotenv())
+
+def llm_model(image_path, extracted_text_path):
+ client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
+
+ # Convert image to base64 Data URL
+ with open(image_path, "rb") as img_file:
+ base64_img = base64.b64encode(img_file.read()).decode("utf-8")
+ data_url = f"data:image/jpeg;base64,{base64_img}" # or image/png
+
+ # Use gpt-4-turbo with vision support
+ response = client.chat.completions.create(
+ model="gpt-4-turbo",
+ messages=[
+ {
+ "role": "user",
+ "content": [
+ {"type": "text", "text": "Fix the spelling and grammar errors of the preceding document "
+ "and reformat the document so that it displays the text in its intended format without adding new information, "
+ "only fixing text that is already there. Print only the result without any additional text or responses."},
+ {"type": "image_url", "image_url": {"url": data_url}}
+ ]
+ }
+ ],
+ max_tokens=500
+ )
+
+ print(response.choices[0].message.content)
+
+# from NLPModel_4 import NLPProcessor
+# def nlp_model_md(document_path):
+# processor = NLPProcessor()
+# LLM_to_NLP = processor.read_file(document_path)
+# processor.nlp_format(LLM_to_NLP)
+# return 'nlp_output.md', processor
+
+# def nlp_model_pdf(document_path, processor):
+# processor.to_pdf(document_path)
+
+
+# cv and llm model combined
+def cv_llm(img_path):
+ # Run computer vision model to extract text into a file
+ extracted_text_path = cv_model(img_path) # This should return a document path, like 'extracted_text.txt'
+
+ # Run LLM with both the image and the extracted document
+ llm_model(img_path, extracted_text_path)
+
+ return 'llm_output.md'
+# test models in flow state
+
+cv_llm(notesImgPath)
+
diff --git a/ML Models/NLPModel_4.py b/ML Models/NLPModel_4.py
new file mode 100644
index 0000000..0a8be61
--- /dev/null
+++ b/ML Models/NLPModel_4.py
@@ -0,0 +1,185 @@
+
+import re
+import nltk
+import os
+from pyhtml2pdf import converter
+from rake_nltk import Rake
+from IPython.core.display import display, HTML
+
+nltk.download('stopwords')
+nltk.download('punkt_tab')
+nltk.download('punkt')
+
+class NLPProcessor:
+
+ # Reading and Processing the Initial Text Data
+ @staticmethod
+ def read_file(original_path):
+ text = open(original_path)
+ text = text.readlines()
+ data = ""
+ for i in text:
+ data += i
+ return data
+
+ # This function aims to replace bullet points in the text with a specific format.
+ # It counts leading spaces to determine the level of bulleting (though the actual logic seems incorrect and might need a review for correct behavior).
+ # It returns the modified lines as a single string joined by newline characters.
+ @staticmethod
+ def replace_bullet_points(text):
+ space = 0
+ replaced_lines = []
+ for i in text:
+ if i == "-":
+ # Replace the leading spaces with dashes
+ text = text.replace(' ', '-', space)
+ replaced_lines.append(text)
+ elif i == " ":
+ space += 1
+ else:
+ break
+ return '\n'.join(replaced_lines)
+
+ # This function aims to replace bold text in the input text with HTML bold tags.
+ @staticmethod
+ def replace_bold(text):
+ stars = ""
+ replaced_lines = []
+ for i in text:
+ if i == "*":
+ # Replace the leading spaces with dashes
+ stars += "*"
+ else:
+ text = text.replace(stars, "", 1)
+ text = text.replace(stars, "", 1)
+ replaced_lines.append(text)
+ break
+
+ return '\n'.join(replaced_lines)
+
+ # This function writes the transformed text (after applying bullet point replacements) back to a new file, "transformed_text.txt".
+ @staticmethod
+ def transform_text(text):
+ transformed_text = []
+ for line in text:
+ if line.strip().startswith('-'):
+ new_line = NLPProcessor.replace_bullet_points(line)
+ transformed_text.append(new_line)
+ else:
+ transformed_text.append(line)
+ return transformed_text
+
+ # This function is used to capitalize the first letter of a matched keyword while preserving the original capitalization of the rest of the word.
+ @staticmethod
+ def capitalize_first_letter(match, keyword):
+ matched_word = match.group(0)
+ if matched_word.lower() == keyword.lower(): # Check if the matched word is the keyword itself
+ return matched_word
+ elif matched_word[0].isupper(): # Check if the first letter is already capitalized
+ return matched_word
+ else:
+ return matched_word.capitalize()
+
+ # This function is used to highlight keywords in the text by wrapping them in HTML span tags with a yellow background color.
+ @staticmethod
+ def highlight_keywords(text):
+ highlighted_strings = []
+ r = Rake()
+ for line in text:
+ # Extract keywords using rake_nltk
+ r.extract_keywords_from_text(line)
+ keywords = r.get_ranked_phrases()
+
+ # Replace keywords with HTML span tags for highlighting while preserving capitalization
+ highlighted_string = line
+ for keyword in keywords:
+ highlighted_string = re.sub(r'\b{}\b'.format(re.escape(keyword)),
+ lambda x: f'{NLPProcessor.capitalize_first_letter(x, keyword)}',
+ highlighted_string, flags=re.IGNORECASE)
+
+ highlighted_strings.append(highlighted_string)
+
+ # Combine highlighted strings into HTML
+ combined_html = ' '.join(highlighted_strings)
+
+ return highlighted_strings
+
+ # This function is used to generate HTML tags for the given text.
+ @staticmethod
+ def bullet_points_formatting(html_path):
+ result = NLPProcessor.highlight_keywords(open("llm_output.txt").readlines())
+ with open(html_path, "w") as file:
+ curr_num = 0 # Current number of leading dashes
+ prev_num = 0 # Previous number of leading dashes
+ fixed_distance = 0 # Fixed distance between current and previous number of leading dashes
+
+ for line_str in result:
+ # Check if the line starts with an asterisk
+ if line_str.startswith('*'):
+ file.write(NLPProcessor.replace_bold(line_str))
+ # Check if the line starts with a dash
+ elif line_str.startswith("-"):
+ # Count the number of leading dashes
+ for i in line_str:
+ if i == "-": # Increment the count of leading dashes
+ curr_num += 1
+ else: # Break the loop if a non-dash character is encountered
+ break
+ # Check the relationship between the current and previous number of leading dashes
+ if curr_num == prev_num:
+ line_str = line_str.replace("-", "", curr_num)
+ line_str = "
" + line_str + "
"
+ file.write(HTML(line_str).data)
+ # Check if the current number of leading dashes is greater than the previous number
+ elif curr_num > prev_num:
+ fixed_distance = curr_num - prev_num
+ file.write("
"
+ file.write(HTML(line_str).data)
+
+ # Check if the current number of leading dashes is less than the previous number
+ else:
+ distance = prev_num - curr_num
+ while distance >= fixed_distance:
+ file.write("
"
+ file.write(HTML(line_str).data)
+
+ prev_num = curr_num
+ curr_num = 0
+
+ # If the line does not start with a dash or asterisk
+ else:
+ while prev_num > 0: # Close any open unordered lists
+ file.write("")
+ prev_num -= 1
+
+ file.write(HTML(line_str).data)
+ file.write(" ")
+
+ # This main function reads the original text file, applies the necessary transformations, highlights keywords, and writes the output to an HTML file.
+ @staticmethod
+ def nlp_format(text_content):
+ transformed_text = NLPProcessor.replace_bullet_points(text_content)
+
+ transformed_text_LLM = "llm_output.md"
+ NLPProcessor.transform_text(transformed_text_LLM)
+
+ highlighted_text = NLPProcessor.highlight_keywords(transformed_text)
+
+ output_path = "nlp_output.md"
+ NLPProcessor.bullet_points_formatting(output_path)
+ @staticmethod
+ def to_pdf(output_path):
+ path = os.path.abspath(output_path)
+ pdf_output_path = 'nlp_output.pdf'
+ converter.convert(f'file:///{path}', pdf_output_path)
+
+# Example usage:
+# processor = NLPProcessor()
+# text_data = processor.read_file('path/to/your/file.txt')
+# processor.nlp_format(text_data)
+
diff --git a/ML Models/Test Images/Prerna Notes.jpg b/ML Models/Test Images/Prerna Notes.jpg
new file mode 100644
index 0000000..3e0090b
Binary files /dev/null and b/ML Models/Test Images/Prerna Notes.jpg differ
diff --git a/ML Models/Test Images/PrernaNotesNoNums.jpg b/ML Models/Test Images/PrernaNotesNoNums.jpg
new file mode 100644
index 0000000..4ccb5cb
Binary files /dev/null and b/ML Models/Test Images/PrernaNotesNoNums.jpg differ
diff --git a/ML Models/Test Images/blank.jpg b/ML Models/Test Images/blank.jpg
new file mode 100644
index 0000000..f612185
Binary files /dev/null and b/ML Models/Test Images/blank.jpg differ
diff --git a/ML Models/Test Images/digital.png b/ML Models/Test Images/digital.png
new file mode 100644
index 0000000..89278cc
Binary files /dev/null and b/ML Models/Test Images/digital.png differ
diff --git a/ML Models/Test Images/notes.png b/ML Models/Test Images/notes.png
new file mode 100644
index 0000000..b888e5e
Binary files /dev/null and b/ML Models/Test Images/notes.png differ
diff --git a/ML Models/__pycache__/NLPModel_4.cpython-310.pyc b/ML Models/__pycache__/NLPModel_4.cpython-310.pyc
new file mode 100644
index 0000000..0262615
Binary files /dev/null and b/ML Models/__pycache__/NLPModel_4.cpython-310.pyc differ
diff --git a/ML Models/cv_output.txt b/ML Models/cv_output.txt
new file mode 100644
index 0000000..f5d7ad7
--- /dev/null
+++ b/ML Models/cv_output.txt
@@ -0,0 +1,4 @@
+ remember thiggstefer
+ nen
+ why
+ Heres
diff --git a/ML Models/llm_output.txt b/ML Models/llm_output.txt
new file mode 100644
index 0000000..945ed42
--- /dev/null
+++ b/ML Models/llm_output.txt
@@ -0,0 +1 @@
+I'm sorry, but there doesn't seem to be enough coherent information in the text you've provided to correct spelling or grammar errors in a meaningful way. The text does not form complete sentences or convey a clear idea. Could you please provide more context or check the text for accuracy?
\ No newline at end of file
diff --git a/cvModel/local_paddleocr_model/rec/inference.pdiparams b/ML Models/local_paddleocr_model/rec/inference.pdiparams
similarity index 100%
rename from cvModel/local_paddleocr_model/rec/inference.pdiparams
rename to ML Models/local_paddleocr_model/rec/inference.pdiparams
diff --git a/cvModel/local_paddleocr_model/rec/inference.pdiparams.info b/ML Models/local_paddleocr_model/rec/inference.pdiparams.info
similarity index 100%
rename from cvModel/local_paddleocr_model/rec/inference.pdiparams.info
rename to ML Models/local_paddleocr_model/rec/inference.pdiparams.info
diff --git a/cvModel/local_paddleocr_model/rec/inference.pdmodel b/ML Models/local_paddleocr_model/rec/inference.pdmodel
similarity index 100%
rename from cvModel/local_paddleocr_model/rec/inference.pdmodel
rename to ML Models/local_paddleocr_model/rec/inference.pdmodel
diff --git a/ML Models/model2.py b/ML Models/model2.py
new file mode 100644
index 0000000..67dc4a6
--- /dev/null
+++ b/ML Models/model2.py
@@ -0,0 +1,72 @@
+
+import os
+import re
+import cv2
+import pandas as pd
+from paddleocr import PaddleOCR
+from difflib import SequenceMatcher
+
+def normalize_text(text):
+ return re.sub(r'[^\w\s]', '', text).lower().strip()
+
+class OCRProcessor:
+ def __init__(self, paddle_language='en'):
+ self.ocr = PaddleOCR(use_angle_cls=True, lang=paddle_language, show_log=False)
+
+ def load_and_preprocess_image(self, image_path):
+ image = cv2.imread(image_path)
+ if image is None:
+ raise FileNotFoundError(f"Image not found: {image_path}")
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
+ _, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
+ return thresh
+
+ def extract_text(self, image):
+ result = self.ocr.ocr(image, cls=True)
+ if result is None:
+ return ""
+ return "\n".join([line[1][0] for box in result if box for line in box if line and len(line) > 1])
+
+
+ def process_image(self, image_path):
+ image = self.load_and_preprocess_image(image_path)
+ return self.extract_text(image)
+
+def calculate_similarity(text1, text2):
+ return SequenceMatcher(None, text1.strip().lower(), text2.strip().lower()).ratio()
+
+
+def evaluate_dataset(dataset_path='C:/Users/prern/OneDrive/Desktop/Codes/OLD codes/CPP/Spring24 Projects/InkWave/dataset/imgs/TrainCVDataSet', tsv_path='C:/Users/prern/OneDrive/Desktop/Codes/OLD codes/CPP/Spring24 Projects/InkWave/dataset/imgs/trainCV.tsv'):
+
+ df = pd.read_csv(tsv_path, sep='\t', header=0, names=['path', 'transcription'])
+
+ # ✂️ Trim paths to just the filenames
+ df['path'] = df['path'].apply(os.path.basename)
+
+ processor = OCRProcessor()
+ total = 0
+ total_similarity = 0
+
+ for index, row in df.iterrows():
+ image_file = os.path.join(dataset_path, row['path'])
+ try:
+ extracted_text = processor.process_image(image_file)
+
+ # Normalize both the OCR output and the ground truth transcription
+ norm_pred = normalize_text(extracted_text)
+ norm_gt = normalize_text(row['transcription'])
+
+ similarity = calculate_similarity(norm_pred, norm_gt)
+ total_similarity += similarity
+ total += 1
+ print(f"[{row['path']}] Similarity: {similarity:.2f}")
+ except FileNotFoundError as e:
+ print(f"Skipping {row['path']}: {e}")
+
+ if total > 0:
+ print(f"\n🔍 Average OCR Accuracy (text similarity): {total_similarity / total:.2%}")
+ else:
+ print("⚠️ No images were processed.")
+
+# Run the evaluation
+#evaluate_dataset()
diff --git a/ML Models/new_model/inference.pdiparams b/ML Models/new_model/inference.pdiparams
new file mode 100644
index 0000000..26ba0c9
Binary files /dev/null and b/ML Models/new_model/inference.pdiparams differ
diff --git a/ML Models/new_model/inference.pdiparams.info b/ML Models/new_model/inference.pdiparams.info
new file mode 100644
index 0000000..1cdccfc
Binary files /dev/null and b/ML Models/new_model/inference.pdiparams.info differ
diff --git a/ML Models/new_model/inference.pdmodel b/ML Models/new_model/inference.pdmodel
new file mode 100644
index 0000000..5dfe4cf
Binary files /dev/null and b/ML Models/new_model/inference.pdmodel differ
diff --git a/ML Models/new_model/rec/inference.pdiparams b/ML Models/new_model/rec/inference.pdiparams
new file mode 100644
index 0000000..26ba0c9
Binary files /dev/null and b/ML Models/new_model/rec/inference.pdiparams differ
diff --git a/ML Models/new_model/rec/inference.pdiparams.info b/ML Models/new_model/rec/inference.pdiparams.info
new file mode 100644
index 0000000..1cdccfc
Binary files /dev/null and b/ML Models/new_model/rec/inference.pdiparams.info differ
diff --git a/ML Models/new_model/rec/inference.pdmodel b/ML Models/new_model/rec/inference.pdmodel
new file mode 100644
index 0000000..5dfe4cf
Binary files /dev/null and b/ML Models/new_model/rec/inference.pdmodel differ
diff --git a/ML Models/requirements.txt b/ML Models/requirements.txt
new file mode 100644
index 0000000..1115d97
--- /dev/null
+++ b/ML Models/requirements.txt
@@ -0,0 +1,111 @@
+albucore==0.0.13
+albumentations==1.4.10
+annotated-types==0.7.0
+anyio==4.6.2.post1
+astor==0.8.1
+asttokens==2.4.1
+attrs==24.2.0
+beautifulsoup4==4.12.3
+boto3==1.35.64
+botocore==1.35.64
+bottle==0.13.2
+certifi==2024.8.30
+cffi==1.17.1
+charset-normalizer==3.4.0
+click==8.1.7
+colorama==0.4.6
+common==0.1.2
+contourpy==1.3.1
+cycler==0.12.1
+Cython==3.0.11
+data==0.4
+decorator==5.1.1
+distro==1.9.0
+dual==0.1.0
+dynamo3==0.4.10
+exceptiongroup==1.2.2
+executing==2.1.0
+fire==0.7.0
+flywheel==0.5.4
+fonttools==4.55.0
+funcsigs==1.0.2
+h11==0.14.0
+httpcore==1.0.7
+httpx==0.27.2
+idna==3.10
+imageio==2.36.0
+imgaug==0.4.0
+ipython==8.29.0
+jedi==0.19.2
+jiter==0.7.1
+jmespath==1.0.1
+joblib==1.4.2
+kiwisolver==1.4.7
+lazy_loader==0.4
+lmdb==1.5.1
+lxml==5.3.0
+matplotlib==3.9.2
+matplotlib-inline==0.1.7
+networkx==3.4.2
+nltk==3.9.1
+numpy==1.26.4
+openai==1.54.4
+opencv-contrib-python==4.10.0.84
+opencv-python==4.10.0.84
+opencv-python-headless==4.10.0.84
+opt-einsum==3.3.0
+outcome==1.3.0.post0
+packaging==24.2
+paddle==1.0.2
+paddleocr==2.9.1
+paddlepaddle==2.6.2
+parso==0.8.4
+peewee==3.17.8
+pillow==11.0.0
+prompt_toolkit==3.0.48
+protobuf==3.20.2
+prox==0.0.17
+pure_eval==0.2.3
+pyclipper==1.3.0.post6
+pycparser==2.22
+pydantic==2.9.2
+pydantic_core==2.23.4
+Pygments==2.18.0
+pyhtml2pdf==0.0.7
+pyparsing==3.2.0
+PySocks==1.7.1
+python-dateutil==2.9.0.post0
+python-docx==1.1.2
+python-dotenv==1.0.1
+python-geoip-python3==1.3
+PyYAML==6.0.2
+rake-nltk==1.0.6
+RapidFuzz==3.10.1
+regex==2024.11.6
+requests==2.32.3
+s3transfer==0.10.3
+scikit-image==0.24.0
+scikit-learn==1.5.2
+scipy==1.14.1
+selenium==4.26.1
+shapely==2.0.6
+six==1.16.0
+sniffio==1.3.1
+sortedcontainers==2.4.0
+soupsieve==2.6
+stack-data==0.6.3
+termcolor==2.5.0
+threadpoolctl==3.5.0
+tifffile==2024.9.20
+tight==0.1.0
+tomli==2.1.0
+tqdm==4.67.0
+traitlets==5.14.3
+trio==0.27.0
+trio-websocket==0.11.1
+typing_extensions==4.12.2
+urllib3==2.2.3
+wcwidth==0.2.13
+webdriver-manager==4.0.2
+websocket-client==1.8.0
+wsproto==1.2.0
diff --git a/ML Models/run_cv_llm.py b/ML Models/run_cv_llm.py
new file mode 100644
index 0000000..dc0ad5d
--- /dev/null
+++ b/ML Models/run_cv_llm.py
@@ -0,0 +1,8 @@
+from models import cv_model, llm_model
+import sys
+
+# allow backend to run cv and llm initially with javascript
+if __name__ == '__main__':
+ img_path = sys.argv[1] # Takes image path from command line argument
+ llm_model(cv_model(img_path))
+ sys.stdout.flush()
\ No newline at end of file
diff --git a/ML Models/run_nlp_md.py b/ML Models/run_nlp_md.py
new file mode 100644
index 0000000..720639e
--- /dev/null
+++ b/ML Models/run_nlp_md.py
@@ -0,0 +1,8 @@
+from models import nlp_model_md
+import sys
+
+# allow backend to run nlp model and get modified md file with javascript
+if __name__ == '__main__':
+ file_path = sys.argv[1] # Takes text file path from command line argument
+ nlp_model_md(file_path)
+ sys.stdout.flush()
\ No newline at end of file
diff --git a/ML Models/run_nlp_pdf.py b/ML Models/run_nlp_pdf.py
new file mode 100644
index 0000000..48ffdcf
--- /dev/null
+++ b/ML Models/run_nlp_pdf.py
@@ -0,0 +1,8 @@
+from models import nlp_model_pdf
+import sys
+
+# allow backend to run pdf converter part of nlp model with javascript
+if __name__ == '__main__':
+ file_path = sys.argv[1] # Takes file path from command line argument
+ nlp_model_pdf(file_path)
+ sys.stdout.flush()
\ No newline at end of file
diff --git a/cvModel/fine_tune.py b/cvModel/fine_tune.py
deleted file mode 100644
index 039c10d..0000000
--- a/cvModel/fine_tune.py
+++ /dev/null
@@ -1,31 +0,0 @@
-from paddleocr import PaddleOCR, draw_ocr
-from PIL import Image
-import os
-
-# Specify the directory of your fine-tuned model and the folder with images
-rec_model_dir = "new_model.h5"
-img_folder_path = "./traindata"
-output_file_path = "./output.txt"
-
-# Initialize PaddleOCR with your fine-tuned model
-ocr = PaddleOCR(rec_model_dir=rec_model_dir, use_angle_cls=True, lang='en')
-
-# Function to process an image and return OCR results
-def process_image(image_path):
- result = ocr.ocr(image_path, cls=True)
- return result
-
-# Iterate over all images in the folder and process them
-with open(output_file_path, 'w') as output_file:
- for img_file in os.listdir(img_folder_path):
- if img_file.endswith((".png", ".jpg", ".jpeg")): # Add other file formats if needed
- img_path = os.path.join(img_folder_path, img_file)
- try:
- ocr_results = process_image(img_path)
- for idx, res in enumerate(ocr_results):
- for line in res:
- output_file.write(f"{img_file},{line[1][0]},{line[1][1]}\n")
- except Exception as e:
- print(f"Error processing {img_file}: {e}")
-
-print("OCR processing complete. Results saved to:", output_file_path)
diff --git a/cv_output.txt b/cv_output.txt
new file mode 100644
index 0000000..9e25ffc
--- /dev/null
+++ b/cv_output.txt
@@ -0,0 +1,10 @@
+A B C DE FG+IJKLMNOPgRSTUWZY
+bird asu Know
+1
+Knows
+can'l
+s.hAm
+heM
+me
+Lord
+tYee
\ No newline at end of file
diff --git a/004949546_00601_8.zip b/models/004949546_00601_8.zip
similarity index 100%
rename from 004949546_00601_8.zip
rename to models/004949546_00601_8.zip
diff --git a/Computer Vision Model/Computer Vision Presentation.pdf b/models/Computer Vision Model/Computer Vision Presentation.pdf
similarity index 100%
rename from Computer Vision Model/Computer Vision Presentation.pdf
rename to models/Computer Vision Model/Computer Vision Presentation.pdf
diff --git a/Data Cleaning and Data Splitting Files/DataCleaningInkwave.py b/models/Data Cleaning and Data Splitting Files/DataCleaningInkwave.py
similarity index 100%
rename from Data Cleaning and Data Splitting Files/DataCleaningInkwave.py
rename to models/Data Cleaning and Data Splitting Files/DataCleaningInkwave.py
diff --git a/Data Cleaning and Data Splitting Files/DataCleaningWithFileTransferForCV.py b/models/Data Cleaning and Data Splitting Files/DataCleaningWithFileTransferForCV.py
similarity index 100%
rename from Data Cleaning and Data Splitting Files/DataCleaningWithFileTransferForCV.py
rename to models/Data Cleaning and Data Splitting Files/DataCleaningWithFileTransferForCV.py
diff --git a/Data Cleaning and Data Splitting Files/DataFilesSplittingCV.py b/models/Data Cleaning and Data Splitting Files/DataFilesSplittingCV.py
similarity index 100%
rename from Data Cleaning and Data Splitting Files/DataFilesSplittingCV.py
rename to models/Data Cleaning and Data Splitting Files/DataFilesSplittingCV.py
diff --git a/Data Cleaning and Data Splitting Files/DataSplittingCVInkwave.ipynb b/models/Data Cleaning and Data Splitting Files/DataSplittingCVInkwave.ipynb
similarity index 100%
rename from Data Cleaning and Data Splitting Files/DataSplittingCVInkwave.ipynb
rename to models/Data Cleaning and Data Splitting Files/DataSplittingCVInkwave.ipynb
diff --git a/Data Cleaning and Data Splitting Files/DataSplittingNLPLearningDataset.ipynb b/models/Data Cleaning and Data Splitting Files/DataSplittingNLPLearningDataset.ipynb
similarity index 100%
rename from Data Cleaning and Data Splitting Files/DataSplittingNLPLearningDataset.ipynb
rename to models/Data Cleaning and Data Splitting Files/DataSplittingNLPLearningDataset.ipynb
diff --git a/Data Cleaning and Data Splitting Files/newTraining.tsv b/models/Data Cleaning and Data Splitting Files/newTraining.tsv
similarity index 100%
rename from Data Cleaning and Data Splitting Files/newTraining.tsv
rename to models/Data Cleaning and Data Splitting Files/newTraining.tsv
diff --git a/Data Set File Locations b/models/Data Set File Locations
similarity index 100%
rename from Data Set File Locations
rename to models/Data Set File Locations
diff --git a/Extra files/test_data.tsv b/models/Extra files/test_data.tsv
similarity index 100%
rename from Extra files/test_data.tsv
rename to models/Extra files/test_data.tsv
diff --git a/InkWave Charts.drawio.html b/models/InkWave Charts.drawio.html
similarity index 100%
rename from InkWave Charts.drawio.html
rename to models/InkWave Charts.drawio.html
diff --git a/InkWave Collab File b/models/InkWave Collab File
similarity index 100%
rename from InkWave Collab File
rename to models/InkWave Collab File
diff --git a/InkWave Collab Programs/CVModel.py b/models/InkWave Collab Programs/CVModel.py
similarity index 83%
rename from InkWave Collab Programs/CVModel.py
rename to models/InkWave Collab Programs/CVModel.py
index 9e310d5..400e90f 100644
--- a/InkWave Collab Programs/CVModel.py
+++ b/models/InkWave Collab Programs/CVModel.py
@@ -1,43 +1,51 @@
-from paddleocr import PaddleOCR
-
-class OCRProcessor:
- def __init__(self, image_path='input_image.jpg', output_path='./input.txt', language='en'):
- """
- Initialize the OCRProcessor.
-
- Args:
- - image_path (str): Path to the input image.
- Default is "input_image.jpg"
- - output_path (str): Path to the output file where OCR results will be saved.
- Default is './input.txt' since it is used as input for the LLM Model.
- - language (str): Language to use for OCR. Default is 'en' (English).
- """
- self.image_path = image_path
- self.output_path = output_path
- self.language = language
-
- # Initialize PaddleOCR with specified language
- self.ocr = PaddleOCR(use_angle_cls=True, lang=language)
-
- def process_image(self):
- """
- Process the input image and perform OCR.
- """
- # Perform OCR on the image
- result = self.ocr.ocr(self.image_path, cls=True)
-
- # Print the OCR results
- for idx in range(len(result)):
- res = result[idx]
- for line in res:
- print(line)
-
- # Write OCR results to a file
- with open(self.output_path, 'w') as file:
- for entry in result:
- for bbox, (text, score) in entry:
- print(text)
- # Add extra spaces before text if x-coordinate of the bounding box is greater than 5
- if bbox[0][0] > 5:
- file.write(" ")
- file.write(f"{text}\n")
+from paddleocr import PaddleOCR
+
+class OCRProcessor:
+ def __init__(self, image_path='input_image.jpg', output_path='./input.txt', language='en'):
+ """
+ Initialize the OCRProcessor.
+
+ Args:
+ - image_path (str): Path to the input image.
+ Default is "input_image.jpg"
+ - output_path (str): Path to the output file where OCR results will be saved.
+ Default is './input.txt' since it is used as input for the LLM Model.
+ - language (str): Language to use for OCR. Default is 'en' (English).
+ """
+ self.image_path = image_path
+ self.output_path = output_path
+ self.language = language
+
+ # Initialize PaddleOCR with specified language
+ self.ocr = PaddleOCR(use_angle_cls=True, lang=language)
+
+<<<<<<<< HEAD:models/cvModel/model.py
+ def process_image(self,image_path):
+========
+ def process_image(self):
+>>>>>>>> temp-ML-frontend-backend:models/InkWave Collab Programs/CVModel.py
+ """
+ Process the input image and perform OCR.
+ """
+ # Perform OCR on the image
+ result = self.ocr.ocr(self.image_path, cls=True)
+
+ # Print the OCR results
+ for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+
+ # Write OCR results to a file
+ with open(self.output_path, 'w') as file:
+ for entry in result:
+ for bbox, (text, score) in entry:
+ print(text)
+ # Add extra spaces before text if x-coordinate of the bounding box is greater than 5
+ if bbox[0][0] > 5:
+ file.write(" ")
+<<<<<<<< HEAD:models/cvModel/model.py
+ file.write(f"{text}\n")
+========
+ file.write(f"{text}\n")
+>>>>>>>> temp-ML-frontend-backend:models/InkWave Collab Programs/CVModel.py
diff --git a/InkWave Collab Programs/LLMModel.py b/models/InkWave Collab Programs/LLMModel.py
similarity index 97%
rename from InkWave Collab Programs/LLMModel.py
rename to models/InkWave Collab Programs/LLMModel.py
index d71c0b1..4d1de18 100644
--- a/InkWave Collab Programs/LLMModel.py
+++ b/models/InkWave Collab Programs/LLMModel.py
@@ -1,101 +1,101 @@
-import os
-from openai import OpenAI
-from dotenv import load_dotenv, find_dotenv
-
-class LLMProcessor:
- def __init__(self):
- """
- Initialize the LLMProcessor.
-
- Load API key from environment variables and set up OpenAI client.
- """
- load_dotenv(find_dotenv())
- self.client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
- self.model = "gpt-4-turbo-preview"
- self.temperature = 0.7
-
- def process_document(self):
- """
- Process the document according to user-selected options.
- """
- try:
- with open('input.txt', 'r') as file:
- input_file = file.read()
- except FileNotFoundError:
- print("File not found. Please check the file name or path.")
- return None
-
- while True:
- # Present user with options
- user_message = input(f"\n===========================================================\
- \n(1) Correct the spelling and grammar errors of the document \
- \n(2) Reformat the document \
- \n(3) Correct spelling/grammar and reformat the document \
- \n(4) Print the current version of the document \
- \n(5) Produce accuracy score \
- \n(6) Quit \
- \n\nPlease select an action to perform on the document: ")
- print("\n")
-
- if user_message == "1": # spelling and grammar correction
- prompt = "Without reformatting the document, this includes creating new lines or altering the spacing in any way and keeping the characters where they are, fix the spelling errors of the following document"
- elif user_message == "2": # reformat the document
- prompt = "Reformat input so that it displays the text in its intended format."
- elif user_message == "3": # correct spelling/grammar and reformat the document
- prompt = "Fix the spelling and grammar errors of the following document and reformat the document so that it displays the text in its intended format. Print only the result without any additional text or responses."
- elif user_message == "4": # print the current version of the document
- output_file = "output.txt"
- try:
- with open(output_file, "r") as file:
- file_content = file.read()
- except IOError: # unable to open file
- print("Error: File Not Found")
- print("--------------------------------------------------------------------------\n"
- + file_content
- + "\n--------------------------------------------------------------------------\n")
- continue
- elif user_message == "5": # produce accuracy score
- prompt = "The following file contains two parts. The first part is the output from a computer vision program after corrections, and the second part is the original text. Please produce an accuracy score accessing how accurate the computer vision output is to the orignal text."
- elif user_message == "6": # Quit
- print("Exiting the program...")
- break
-
- # Prepare the API request with the file contents and selected prompt
- messages = [
- {"role": "system", "content": input_file},
- {"role": "user", "content": prompt},
- ]
- print("Processing, please wait...")
- self.write_to_output(messages)
- print("Done\n")
-
- def get_summary(self, messages):
- """
- Make API request to OpenAI for summary.
-
- Args:
- - messages (list): List of messages for the conversation.
-
- Returns:
- - str: Summary of the conversation from OpenAI.
- """
- completion = self.client.chat.completions.create(
- model=self.model,
- messages=messages,
- temperature=self.temperature,
- )
- return completion.choices[0].message.content
-
- def write_to_output(self, messages):
- """
- Write changes to output file.
-
- Args:
- - messages (list): List of messages for the conversation.
- """
- output_file = "output.txt"
- try:
- with open(output_file, "w") as file:
- file.write(self.get_summary(messages))
- except IOError: # unable to open file
- print("Error: Unable to write to the file.")
+import os
+from openai import OpenAI
+from dotenv import load_dotenv, find_dotenv
+
+class LLMProcessor:
+ def __init__(self):
+ """
+ Initialize the LLMProcessor.
+
+ Load API key from environment variables and set up OpenAI client.
+ """
+ load_dotenv(find_dotenv())
+ self.client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
+ self.model = "gpt-4-turbo-preview"
+ self.temperature = 0.7
+
+ def process_document(self):
+ """
+ Process the document according to user-selected options.
+ """
+ try:
+ with open('input.txt', 'r') as file:
+ input_file = file.read()
+ except FileNotFoundError:
+ print("File not found. Please check the file name or path.")
+ return None
+
+ while True:
+ # Present user with options
+ user_message = input(f"\n===========================================================\
+ \n(1) Correct the spelling and grammar errors of the document \
+ \n(2) Reformat the document \
+ \n(3) Correct spelling/grammar and reformat the document \
+ \n(4) Print the current version of the document \
+ \n(5) Produce accuracy score \
+ \n(6) Quit \
+ \n\nPlease select an action to perform on the document: ")
+ print("\n")
+
+ if user_message == "1": # spelling and grammar correction
+ prompt = "Without reformatting the document, this includes creating new lines or altering the spacing in any way and keeping the characters where they are, fix the spelling errors of the following document"
+ elif user_message == "2": # reformat the document
+ prompt = "Reformat input so that it displays the text in its intended format."
+ elif user_message == "3": # correct spelling/grammar and reformat the document
+ prompt = "Fix the spelling and grammar errors of the following document and reformat the document so that it displays the text in its intended format. Print only the result without any additional text or responses."
+ elif user_message == "4": # print the current version of the document
+ output_file = "output.txt"
+ try:
+ with open(output_file, "r") as file:
+ file_content = file.read()
+ except IOError: # unable to open file
+ print("Error: File Not Found")
+ print("--------------------------------------------------------------------------\n"
+ + file_content
+ + "\n--------------------------------------------------------------------------\n")
+ continue
+ elif user_message == "5": # produce accuracy score
+ prompt = "The following file contains two parts. The first part is the output from a computer vision program after corrections, and the second part is the original text. Please produce an accuracy score accessing how accurate the computer vision output is to the orignal text."
+ elif user_message == "6": # Quit
+ print("Exiting the program...")
+ break
+
+ # Prepare the API request with the file contents and selected prompt
+ messages = [
+ {"role": "system", "content": input_file},
+ {"role": "user", "content": prompt},
+ ]
+ print("Processing, please wait...")
+ self.write_to_output(messages)
+ print("Done\n")
+
+ def get_summary(self, messages):
+ """
+ Make API request to OpenAI for summary.
+
+ Args:
+ - messages (list): List of messages for the conversation.
+
+ Returns:
+ - str: Summary of the conversation from OpenAI.
+ """
+ completion = self.client.chat.completions.create(
+ model=self.model,
+ messages=messages,
+ temperature=self.temperature,
+ )
+ return completion.choices[0].message.content
+
+ def write_to_output(self, messages):
+ """
+ Write changes to output file.
+
+ Args:
+ - messages (list): List of messages for the conversation.
+ """
+ output_file = "output.txt"
+ try:
+ with open(output_file, "w") as file:
+ file.write(self.get_summary(messages))
+ except IOError: # unable to open file
+ print("Error: Unable to write to the file.")
diff --git a/InkWaveModelChaining.ipynb b/models/InkWaveModelChaining.ipynb
similarity index 100%
rename from InkWaveModelChaining.ipynb
rename to models/InkWaveModelChaining.ipynb
diff --git a/Large Language Model/Final LLM Model/accuracy_score_input.txt b/models/Large Language Model/Final LLM Model/accuracy_score_input.txt
similarity index 97%
rename from Large Language Model/Final LLM Model/accuracy_score_input.txt
rename to models/Large Language Model/Final LLM Model/accuracy_score_input.txt
index 1446851..7d732c3 100644
--- a/Large Language Model/Final LLM Model/accuracy_score_input.txt
+++ b/models/Large Language Model/Final LLM Model/accuracy_score_input.txt
@@ -1,63 +1,63 @@
-Basic Notes
-Anng
-10/9/12
-Study Tips
-Prepare before class
-review notes from last class
-look through chapter to understand basic idea of
-this class
-May take 20-30 mins but makes a huge difference
-When in doubt, write it down
-Point wste everything though Main Points Main Ideas
-Don't spend too long deciding If you should
-Write down
-Think your notes
-Don't write everything Leave out words
-use abbreviations-include a key of specific-for-
-this lecture abbreviations. e.g. A lecture about
-King Henry VIII will require you to repeat his name
-several times. instead of writing it out each
-Time make a note King Henry VIII=H8 if that
-would be confused with King Henry VII abbreviate
-it as King Henry VIII=8
-know your prof
-use the note-taking method that will best help
-you with this particular prof's lectures
-A more organized prof works well with the
-Cornell style. use the note-taking method
-for visual learners for a prof that jumps
-around and returns to previous points
-Show the prof your notes after the first
-lecture and ask if you are hitting main points.
-
-Basic Notes
-Anna
-10/9/13
-Study Tips
-Prepare before class
-review notes from last class
-look through chapter to understand basic idea of
-this class
-May take 20-30 mins, but makes a huge difference
-When in doubt, write it down
-Don't write everything, though! (Main Points, Main Ideas)
-Don't spend too long deciding if you should
-write it down
-Thin your notes
-Don't write everything! Leave out words
-Use abbreviations-include a key of specific-for-
-this lecture abbreviations. e.g: A lecture about
-King Henry VIII will require you to repeat his name
-several times. Instead of writing it out each
-time, make a note: King Henry VIII=H, Or, if that
-would be confused with King Henry VII, abbreviate
-it as: King Henry VIII=8.
-Know your prof
-Use the note-taking method that will best help
-you with this particular prof's lectures.
-A more organized prof works well with the
-Cornell style. Use the note-taking method
-for usual learners for a prof that jumps
-around and returns to previous points.
-Show the prof your notes after the first
-lecture and ask if you are hitting main points.
+Basic Notes
+Anng
+10/9/12
+Study Tips
+Prepare before class
+review notes from last class
+look through chapter to understand basic idea of
+this class
+May take 20-30 mins but makes a huge difference
+When in doubt, write it down
+Point wste everything though Main Points Main Ideas
+Don't spend too long deciding If you should
+Write down
+Think your notes
+Don't write everything Leave out words
+use abbreviations-include a key of specific-for-
+this lecture abbreviations. e.g. A lecture about
+King Henry VIII will require you to repeat his name
+several times. instead of writing it out each
+Time make a note King Henry VIII=H8 if that
+would be confused with King Henry VII abbreviate
+it as King Henry VIII=8
+know your prof
+use the note-taking method that will best help
+you with this particular prof's lectures
+A more organized prof works well with the
+Cornell style. use the note-taking method
+for visual learners for a prof that jumps
+around and returns to previous points
+Show the prof your notes after the first
+lecture and ask if you are hitting main points.
+
+Basic Notes
+Anna
+10/9/13
+Study Tips
+Prepare before class
+review notes from last class
+look through chapter to understand basic idea of
+this class
+May take 20-30 mins, but makes a huge difference
+When in doubt, write it down
+Don't write everything, though! (Main Points, Main Ideas)
+Don't spend too long deciding if you should
+write it down
+Thin your notes
+Don't write everything! Leave out words
+Use abbreviations-include a key of specific-for-
+this lecture abbreviations. e.g: A lecture about
+King Henry VIII will require you to repeat his name
+several times. Instead of writing it out each
+time, make a note: King Henry VIII=H, Or, if that
+would be confused with King Henry VII, abbreviate
+it as: King Henry VIII=8.
+Know your prof
+Use the note-taking method that will best help
+you with this particular prof's lectures.
+A more organized prof works well with the
+Cornell style. Use the note-taking method
+for usual learners for a prof that jumps
+around and returns to previous points.
+Show the prof your notes after the first
+lecture and ask if you are hitting main points.
diff --git a/Large Language Model/LLM - Kareem/LLM v3/accuracy_score_output.txt b/models/Large Language Model/Final LLM Model/accuracy_score_output.txt
similarity index 98%
rename from Large Language Model/LLM - Kareem/LLM v3/accuracy_score_output.txt
rename to models/Large Language Model/Final LLM Model/accuracy_score_output.txt
index 1675bbd..e660ab4 100644
--- a/Large Language Model/LLM - Kareem/LLM v3/accuracy_score_output.txt
+++ b/models/Large Language Model/Final LLM Model/accuracy_score_output.txt
@@ -1,30 +1,30 @@
-To calculate the accuracy score, we will compare the corrected computer vision output (which we will call the "corrected text") with the original text. The accuracy score will be determined by evaluating how closely the corrected text matches the original text. The evaluation will be based on the number of errors, which includes any incorrect words, missing words, or extra words, as well as any punctuation or formatting differences.
-
-**Corrected Text:**
-
-1. "Anng" should be "Anna"
-2. Date is consistent.
-3. Preparation instructions before class are correctly mirrored.
-4. The advice about note-taking, such as when in doubt, write it down, and the emphasis on main points and ideas, is accurately transmitted.
-5. The guidance on using abbreviations and the example given about King Henry VIII are accurate, but there's a slight variation in the abbreviation for King Henry VIII ("H8" vs. "H," and the clarification regarding confusion with King Henry VII).
-6. The segment about knowing your professor and adapting note-taking methods is correctly reflected, including the specific mention of the Cornell style and visual learners.
-7. The suggestion to show notes to the professor for feedback is accurately conveyed.
-
-**Errors Identified:**
-
-1. Name: "Anng" vs. "Anna"
-2. Abbreviation for King Henry VIII: "H8" vs. "H" (and the subsequent clarification regarding confusion with King Henry VII is slightly different but conveys the same idea).
-3. Minor punctuation and formatting differences (e.g., use of parentheses and commas).
-
-**Assessment:**
-
-- The main body of the text is highly accurate, with the essence of the study tips and note-taking strategies correctly captured.
-- There's a minor error in the name ("Anng" vs. "Anna").
-- The minor differences in the abbreviation for King Henry VIII do not change the overall meaning or utility of the advice given.
-- Punctuation and formatting discrepancies are minor and do not substantially impact the readability or the conveyance of information.
-
-Given the high level of detail and fidelity to the original content, with only minor discrepancies, we can estimate the accuracy to be very high. For a numerical estimation, assuming a simple model where each identified issue (name error, slight abbreviation difference, minor punctuation/formatting) counts as a minor error, and given the overall length and complexity of the text:
-
-- If we were to assign a numerical value, assuming approximately 3 minor errors in a text of this detail and length, the accuracy might be estimated at approximately 98-99%. This is because the errors are very minor in the context of the entire text, and the core content is accurately preserved and conveyed.
-
-This is a subjective assessment, as the exact scoring can depend on the specific criteria and weight assigned to different types of errors (e.g., content vs. formatting).
+To calculate the accuracy score, we will compare the corrected computer vision output (which we will call the "corrected text") with the original text. The accuracy score will be determined by evaluating how closely the corrected text matches the original text. The evaluation will be based on the number of errors, which includes any incorrect words, missing words, or extra words, as well as any punctuation or formatting differences.
+
+**Corrected Text:**
+
+1. "Anng" should be "Anna"
+2. Date is consistent.
+3. Preparation instructions before class are correctly mirrored.
+4. The advice about note-taking, such as when in doubt, write it down, and the emphasis on main points and ideas, is accurately transmitted.
+5. The guidance on using abbreviations and the example given about King Henry VIII are accurate, but there's a slight variation in the abbreviation for King Henry VIII ("H8" vs. "H," and the clarification regarding confusion with King Henry VII).
+6. The segment about knowing your professor and adapting note-taking methods is correctly reflected, including the specific mention of the Cornell style and visual learners.
+7. The suggestion to show notes to the professor for feedback is accurately conveyed.
+
+**Errors Identified:**
+
+1. Name: "Anng" vs. "Anna"
+2. Abbreviation for King Henry VIII: "H8" vs. "H" (and the subsequent clarification regarding confusion with King Henry VII is slightly different but conveys the same idea).
+3. Minor punctuation and formatting differences (e.g., use of parentheses and commas).
+
+**Assessment:**
+
+- The main body of the text is highly accurate, with the essence of the study tips and note-taking strategies correctly captured.
+- There's a minor error in the name ("Anng" vs. "Anna").
+- The minor differences in the abbreviation for King Henry VIII do not change the overall meaning or utility of the advice given.
+- Punctuation and formatting discrepancies are minor and do not substantially impact the readability or the conveyance of information.
+
+Given the high level of detail and fidelity to the original content, with only minor discrepancies, we can estimate the accuracy to be very high. For a numerical estimation, assuming a simple model where each identified issue (name error, slight abbreviation difference, minor punctuation/formatting) counts as a minor error, and given the overall length and complexity of the text:
+
+- If we were to assign a numerical value, assuming approximately 3 minor errors in a text of this detail and length, the accuracy might be estimated at approximately 98-99%. This is because the errors are very minor in the context of the entire text, and the core content is accurately preserved and conveyed.
+
+This is a subjective assessment, as the exact scoring can depend on the specific criteria and weight assigned to different types of errors (e.g., content vs. formatting).
diff --git a/Large Language Model/Final LLM Model/main.py b/models/Large Language Model/Final LLM Model/main.py
similarity index 97%
rename from Large Language Model/Final LLM Model/main.py
rename to models/Large Language Model/Final LLM Model/main.py
index efa71c3..76c2dd3 100644
--- a/Large Language Model/Final LLM Model/main.py
+++ b/models/Large Language Model/Final LLM Model/main.py
@@ -1,103 +1,103 @@
-import os
-from openai import OpenAI
-from dotenv import load_dotenv, find_dotenv
-
-class LLMProcessor:
- def __init__(self):
- """
- Initialize the LLMProcessor.
-
- Load API key from environment variables and set up OpenAI client.
- """
- load_dotenv(find_dotenv())
- self.client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
- self.model = "gpt-4-turbo-preview"
- self.temperature = 0.7
-
- def process_document(self, document_path):
- """
- Process the document according to user-selected options.
- """
- try:
- with open(document_path, 'r') as file:
- input_file = file.read()
- except FileNotFoundError:
- print("File not found. Please check the file name or path.")
- return None
-
- while True:
- # Present user with options
- user_message = input(f"\n===========================================================\
- \n(1) Correct the spelling and grammar errors of the document \
- \n(2) Reformat the document \
- \n(3) Correct spelling/grammar and reformat the document \
- \n(4) Print the current version of the document \
- \n(5) Produce accuracy score \
- \n(6) Quit \
- \n\nPlease select an action to perform on the document: ")
- print("\n")
-
- if user_message == "1": # spelling and grammar correction
- prompt = "Without reformatting the document, this includes creating new lines or altering the spacing in any way and keeping the characters where they are, fix the spelling errors of the following document"
- elif user_message == "2": # reformat the document
- prompt = "Reformat input so that it displays the text in its intended format."
- elif user_message == "3": # correct spelling/grammar and reformat the document
- prompt = "Fix the spelling and grammar errors of the following document and reformat the document so that it displays the text in its intended format. Print only the result without any additional text or responses."
- elif user_message == "4": # print the current version of the document
- output_file = "output.txt"
- try:
- with open(output_file, "r") as file:
- file_content = file.read()
- except IOError: # unable to open file
- print("Error: File Not Found")
- print("--------------------------------------------------------------------------\n"
- + file_content
- + "\n--------------------------------------------------------------------------\n")
- continue
- elif user_message == "5": # produce accuracy score
- prompt = "The following file contains two parts. The first part is the output from a computer vision program after corrections, and the second part is the original text. Please produce an accuracy score accessing how accurate the computer vision output is to the orignal text."
- elif user_message == "6": # Quit
- print("Exiting the program...")
- break
-
- # Prepare the API request with the file contents and selected prompt
- messages = [
- {"role": "system", "content": input_file},
- {"role": "user", "content": prompt},
- ]
- print("Processing, please wait...")
- self.write_to_output(messages)
- print("Done\n")
-
- return self.get_summary(messages)
-
- def get_summary(self, messages):
- """
- Make API request to OpenAI for summary.
-
- Args:
- - messages (list): List of messages for the conversation.
-
- Returns:
- - str: Summary of the conversation from OpenAI.
- """
- completion = self.client.chat.completions.create(
- model=self.model,
- messages=messages,
- temperature=self.temperature,
- )
- return completion.choices[0].message.content
-
- def write_to_output(self, messages):
- """
- Write changes to output file.
-
- Args:
- - messages (list): List of messages for the conversation.
- """
- output_file = "output.txt"
- try:
- with open(output_file, "w") as file:
- file.write(self.get_summary(messages))
- except IOError: # unable to open file
- print("Error: Unable to write to the file.")
+import os
+from openai import OpenAI
+from dotenv import load_dotenv, find_dotenv
+
+class LLMProcessor:
+ def __init__(self):
+ """
+ Initialize the LLMProcessor.
+
+ Load API key from environment variables and set up OpenAI client.
+ """
+ load_dotenv(find_dotenv())
+ self.client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))
+ self.model = "gpt-4-turbo-preview"
+ self.temperature = 0.7
+
+ def process_document(self, document_path):
+ """
+ Process the document according to user-selected options.
+ """
+ try:
+ with open(document_path, 'r') as file:
+ input_file = file.read()
+ except FileNotFoundError:
+ print("File not found. Please check the file name or path.")
+ return None
+
+ while True:
+ # Present user with options
+ user_message = input(f"\n===========================================================\
+ \n(1) Correct the spelling and grammar errors of the document \
+ \n(2) Reformat the document \
+ \n(3) Correct spelling/grammar and reformat the document \
+ \n(4) Print the current version of the document \
+ \n(5) Produce accuracy score \
+ \n(6) Quit \
+ \n\nPlease select an action to perform on the document: ")
+ print("\n")
+
+ if user_message == "1": # spelling and grammar correction
+ prompt = "Without reformatting the document, this includes creating new lines or altering the spacing in any way and keeping the characters where they are, fix the spelling errors of the following document"
+ elif user_message == "2": # reformat the document
+ prompt = "Reformat input so that it displays the text in its intended format."
+ elif user_message == "3": # correct spelling/grammar and reformat the document
+ prompt = "Fix the spelling and grammar errors of the following document and reformat the document so that it displays the text in its intended format. Print only the result without any additional text or responses."
+ elif user_message == "4": # print the current version of the document
+ output_file = "output.txt"
+ try:
+ with open(output_file, "r") as file:
+ file_content = file.read()
+ except IOError: # unable to open file
+ print("Error: File Not Found")
+ print("--------------------------------------------------------------------------\n"
+ + file_content
+ + "\n--------------------------------------------------------------------------\n")
+ continue
+ elif user_message == "5": # produce accuracy score
+ prompt = "The following file contains two parts. The first part is the output from a computer vision program after corrections, and the second part is the original text. Please produce an accuracy score accessing how accurate the computer vision output is to the orignal text."
+ elif user_message == "6": # Quit
+ print("Exiting the program...")
+ break
+
+ # Prepare the API request with the file contents and selected prompt
+ messages = [
+ {"role": "system", "content": input_file},
+ {"role": "user", "content": prompt},
+ ]
+ print("Processing, please wait...")
+ self.write_to_output(messages)
+ print("Done\n")
+
+ return self.get_summary(messages)
+
+ def get_summary(self, messages):
+ """
+ Make API request to OpenAI for summary.
+
+ Args:
+ - messages (list): List of messages for the conversation.
+
+ Returns:
+ - str: Summary of the conversation from OpenAI.
+ """
+ completion = self.client.chat.completions.create(
+ model=self.model,
+ messages=messages,
+ temperature=self.temperature,
+ )
+ return completion.choices[0].message.content
+
+ def write_to_output(self, messages):
+ """
+ Write changes to output file.
+
+ Args:
+ - messages (list): List of messages for the conversation.
+ """
+ output_file = "output.txt"
+ try:
+ with open(output_file, "w") as file:
+ file.write(self.get_summary(messages))
+ except IOError: # unable to open file
+ print("Error: Unable to write to the file.")
diff --git a/Natural Language Processing Model/LLM_preformatted.txt b/models/Large Language Model/Final LLM Model/reformatted_output.txt
similarity index 100%
rename from Natural Language Processing Model/LLM_preformatted.txt
rename to models/Large Language Model/Final LLM Model/reformatted_output.txt
diff --git a/Large Language Model/Final LLM Model/unformatted_input.txt b/models/Large Language Model/Final LLM Model/unformatted_input.txt
similarity index 97%
rename from Large Language Model/Final LLM Model/unformatted_input.txt
rename to models/Large Language Model/Final LLM Model/unformatted_input.txt
index c972542..452a9e1 100644
--- a/Large Language Model/Final LLM Model/unformatted_input.txt
+++ b/models/Large Language Model/Final LLM Model/unformatted_input.txt
@@ -1,31 +1,31 @@
-Basic Notes
-Anng
-10/9/12
-Study Tips
-Prepare before class
-review notes from last class
-look through chapter to understand basic idea of
-this class
-May take 20-30 mins but makes a huge difference
-When in doubt, write it down
-Point wste everything though Main Points Main Ideas
-Don't spend too long deciding If you should
-Write down
-Think your notes
-Don't write everything Leave out words
-use abbreviations-include a key of specific-for-
-this lecture abbreviations. e.g. A lecture about
-King Henry VIII will require you to repeat his name
-several times. instead of writing it out each
-Time make a note King Henry VIII=H8 if that
-would be confused with King Henry VII abbreviate
-it as King Henry VIII=8
-know your prof
-use the note-taking method that will best help
-you with this particular prof's lectures
-A more organized prof works well with the
-Cornell style. use the note-taking method
-for visual learners for a prof that jumps
-around and returns to previous points
-Show the prof your notes after the first
+Basic Notes
+Anng
+10/9/12
+Study Tips
+Prepare before class
+review notes from last class
+look through chapter to understand basic idea of
+this class
+May take 20-30 mins but makes a huge difference
+When in doubt, write it down
+Point wste everything though Main Points Main Ideas
+Don't spend too long deciding If you should
+Write down
+Think your notes
+Don't write everything Leave out words
+use abbreviations-include a key of specific-for-
+this lecture abbreviations. e.g. A lecture about
+King Henry VIII will require you to repeat his name
+several times. instead of writing it out each
+Time make a note King Henry VIII=H8 if that
+would be confused with King Henry VII abbreviate
+it as King Henry VIII=8
+know your prof
+use the note-taking method that will best help
+you with this particular prof's lectures
+A more organized prof works well with the
+Cornell style. use the note-taking method
+for visual learners for a prof that jumps
+around and returns to previous points
+Show the prof your notes after the first
lecture and ask if you are hitting main points.
\ No newline at end of file
diff --git a/Large Language Model/LLM - Kareem/LLM v1/app.ipynb b/models/Large Language Model/LLM - Kareem/LLM v1/app.ipynb
similarity index 100%
rename from Large Language Model/LLM - Kareem/LLM v1/app.ipynb
rename to models/Large Language Model/LLM - Kareem/LLM v1/app.ipynb
diff --git a/Large Language Model/LLM - Kareem/LLM v1/data/context.txt b/models/Large Language Model/LLM - Kareem/LLM v1/data/context.txt
similarity index 100%
rename from Large Language Model/LLM - Kareem/LLM v1/data/context.txt
rename to models/Large Language Model/LLM - Kareem/LLM v1/data/context.txt
diff --git a/Large Language Model/LLM - Kareem/LLM v1/data/output.txt b/models/Large Language Model/LLM - Kareem/LLM v1/data/output.txt
similarity index 100%
rename from Large Language Model/LLM - Kareem/LLM v1/data/output.txt
rename to models/Large Language Model/LLM - Kareem/LLM v1/data/output.txt
diff --git a/Large Language Model/LLM - Kareem/LLM v1/data/prompt.txt b/models/Large Language Model/LLM - Kareem/LLM v1/data/prompt.txt
similarity index 100%
rename from Large Language Model/LLM - Kareem/LLM v1/data/prompt.txt
rename to models/Large Language Model/LLM - Kareem/LLM v1/data/prompt.txt
diff --git a/Large Language Model/LLM - Kareem/LLM v2/app.py b/models/Large Language Model/LLM - Kareem/LLM v2/app.py
similarity index 100%
rename from Large Language Model/LLM - Kareem/LLM v2/app.py
rename to models/Large Language Model/LLM - Kareem/LLM v2/app.py
diff --git a/Large Language Model/LLM - Kareem/LLM v2/corrected text showcase.txt b/models/Large Language Model/LLM - Kareem/LLM v2/corrected text showcase.txt
similarity index 100%
rename from Large Language Model/LLM - Kareem/LLM v2/corrected text showcase.txt
rename to models/Large Language Model/LLM - Kareem/LLM v2/corrected text showcase.txt
diff --git a/Large Language Model/LLM - Kareem/LLM v2/data/input.txt b/models/Large Language Model/LLM - Kareem/LLM v2/data/input.txt
similarity index 100%
rename from Large Language Model/LLM - Kareem/LLM v2/data/input.txt
rename to models/Large Language Model/LLM - Kareem/LLM v2/data/input.txt
diff --git a/Large Language Model/LLM - Kareem/LLM v2/data/output.txt b/models/Large Language Model/LLM - Kareem/LLM v2/data/output.txt
similarity index 100%
rename from Large Language Model/LLM - Kareem/LLM v2/data/output.txt
rename to models/Large Language Model/LLM - Kareem/LLM v2/data/output.txt
diff --git a/Large Language Model/LLM - Kareem/LLM v3/accuracy_score_input.txt b/models/Large Language Model/LLM - Kareem/LLM v3/accuracy_score_input.txt
similarity index 97%
rename from Large Language Model/LLM - Kareem/LLM v3/accuracy_score_input.txt
rename to models/Large Language Model/LLM - Kareem/LLM v3/accuracy_score_input.txt
index 1446851..7d732c3 100644
--- a/Large Language Model/LLM - Kareem/LLM v3/accuracy_score_input.txt
+++ b/models/Large Language Model/LLM - Kareem/LLM v3/accuracy_score_input.txt
@@ -1,63 +1,63 @@
-Basic Notes
-Anng
-10/9/12
-Study Tips
-Prepare before class
-review notes from last class
-look through chapter to understand basic idea of
-this class
-May take 20-30 mins but makes a huge difference
-When in doubt, write it down
-Point wste everything though Main Points Main Ideas
-Don't spend too long deciding If you should
-Write down
-Think your notes
-Don't write everything Leave out words
-use abbreviations-include a key of specific-for-
-this lecture abbreviations. e.g. A lecture about
-King Henry VIII will require you to repeat his name
-several times. instead of writing it out each
-Time make a note King Henry VIII=H8 if that
-would be confused with King Henry VII abbreviate
-it as King Henry VIII=8
-know your prof
-use the note-taking method that will best help
-you with this particular prof's lectures
-A more organized prof works well with the
-Cornell style. use the note-taking method
-for visual learners for a prof that jumps
-around and returns to previous points
-Show the prof your notes after the first
-lecture and ask if you are hitting main points.
-
-Basic Notes
-Anna
-10/9/13
-Study Tips
-Prepare before class
-review notes from last class
-look through chapter to understand basic idea of
-this class
-May take 20-30 mins, but makes a huge difference
-When in doubt, write it down
-Don't write everything, though! (Main Points, Main Ideas)
-Don't spend too long deciding if you should
-write it down
-Thin your notes
-Don't write everything! Leave out words
-Use abbreviations-include a key of specific-for-
-this lecture abbreviations. e.g: A lecture about
-King Henry VIII will require you to repeat his name
-several times. Instead of writing it out each
-time, make a note: King Henry VIII=H, Or, if that
-would be confused with King Henry VII, abbreviate
-it as: King Henry VIII=8.
-Know your prof
-Use the note-taking method that will best help
-you with this particular prof's lectures.
-A more organized prof works well with the
-Cornell style. Use the note-taking method
-for usual learners for a prof that jumps
-around and returns to previous points.
-Show the prof your notes after the first
-lecture and ask if you are hitting main points.
+Basic Notes
+Anng
+10/9/12
+Study Tips
+Prepare before class
+review notes from last class
+look through chapter to understand basic idea of
+this class
+May take 20-30 mins but makes a huge difference
+When in doubt, write it down
+Point wste everything though Main Points Main Ideas
+Don't spend too long deciding If you should
+Write down
+Think your notes
+Don't write everything Leave out words
+use abbreviations-include a key of specific-for-
+this lecture abbreviations. e.g. A lecture about
+King Henry VIII will require you to repeat his name
+several times. instead of writing it out each
+Time make a note King Henry VIII=H8 if that
+would be confused with King Henry VII abbreviate
+it as King Henry VIII=8
+know your prof
+use the note-taking method that will best help
+you with this particular prof's lectures
+A more organized prof works well with the
+Cornell style. use the note-taking method
+for visual learners for a prof that jumps
+around and returns to previous points
+Show the prof your notes after the first
+lecture and ask if you are hitting main points.
+
+Basic Notes
+Anna
+10/9/13
+Study Tips
+Prepare before class
+review notes from last class
+look through chapter to understand basic idea of
+this class
+May take 20-30 mins, but makes a huge difference
+When in doubt, write it down
+Don't write everything, though! (Main Points, Main Ideas)
+Don't spend too long deciding if you should
+write it down
+Thin your notes
+Don't write everything! Leave out words
+Use abbreviations-include a key of specific-for-
+this lecture abbreviations. e.g: A lecture about
+King Henry VIII will require you to repeat his name
+several times. Instead of writing it out each
+time, make a note: King Henry VIII=H, Or, if that
+would be confused with King Henry VII, abbreviate
+it as: King Henry VIII=8.
+Know your prof
+Use the note-taking method that will best help
+you with this particular prof's lectures.
+A more organized prof works well with the
+Cornell style. Use the note-taking method
+for usual learners for a prof that jumps
+around and returns to previous points.
+Show the prof your notes after the first
+lecture and ask if you are hitting main points.
diff --git a/Large Language Model/Final LLM Model/accuracy_score_output.txt b/models/Large Language Model/LLM - Kareem/LLM v3/accuracy_score_output.txt
similarity index 98%
rename from Large Language Model/Final LLM Model/accuracy_score_output.txt
rename to models/Large Language Model/LLM - Kareem/LLM v3/accuracy_score_output.txt
index 1675bbd..e660ab4 100644
--- a/Large Language Model/Final LLM Model/accuracy_score_output.txt
+++ b/models/Large Language Model/LLM - Kareem/LLM v3/accuracy_score_output.txt
@@ -1,30 +1,30 @@
-To calculate the accuracy score, we will compare the corrected computer vision output (which we will call the "corrected text") with the original text. The accuracy score will be determined by evaluating how closely the corrected text matches the original text. The evaluation will be based on the number of errors, which includes any incorrect words, missing words, or extra words, as well as any punctuation or formatting differences.
-
-**Corrected Text:**
-
-1. "Anng" should be "Anna"
-2. Date is consistent.
-3. Preparation instructions before class are correctly mirrored.
-4. The advice about note-taking, such as when in doubt, write it down, and the emphasis on main points and ideas, is accurately transmitted.
-5. The guidance on using abbreviations and the example given about King Henry VIII are accurate, but there's a slight variation in the abbreviation for King Henry VIII ("H8" vs. "H," and the clarification regarding confusion with King Henry VII).
-6. The segment about knowing your professor and adapting note-taking methods is correctly reflected, including the specific mention of the Cornell style and visual learners.
-7. The suggestion to show notes to the professor for feedback is accurately conveyed.
-
-**Errors Identified:**
-
-1. Name: "Anng" vs. "Anna"
-2. Abbreviation for King Henry VIII: "H8" vs. "H" (and the subsequent clarification regarding confusion with King Henry VII is slightly different but conveys the same idea).
-3. Minor punctuation and formatting differences (e.g., use of parentheses and commas).
-
-**Assessment:**
-
-- The main body of the text is highly accurate, with the essence of the study tips and note-taking strategies correctly captured.
-- There's a minor error in the name ("Anng" vs. "Anna").
-- The minor differences in the abbreviation for King Henry VIII do not change the overall meaning or utility of the advice given.
-- Punctuation and formatting discrepancies are minor and do not substantially impact the readability or the conveyance of information.
-
-Given the high level of detail and fidelity to the original content, with only minor discrepancies, we can estimate the accuracy to be very high. For a numerical estimation, assuming a simple model where each identified issue (name error, slight abbreviation difference, minor punctuation/formatting) counts as a minor error, and given the overall length and complexity of the text:
-
-- If we were to assign a numerical value, assuming approximately 3 minor errors in a text of this detail and length, the accuracy might be estimated at approximately 98-99%. This is because the errors are very minor in the context of the entire text, and the core content is accurately preserved and conveyed.
-
-This is a subjective assessment, as the exact scoring can depend on the specific criteria and weight assigned to different types of errors (e.g., content vs. formatting).
+To calculate the accuracy score, we will compare the corrected computer vision output (which we will call the "corrected text") with the original text. The accuracy score will be determined by evaluating how closely the corrected text matches the original text. The evaluation will be based on the number of errors, which includes any incorrect words, missing words, or extra words, as well as any punctuation or formatting differences.
+
+**Corrected Text:**
+
+1. "Anng" should be "Anna"
+2. Date is consistent.
+3. Preparation instructions before class are correctly mirrored.
+4. The advice about note-taking, such as when in doubt, write it down, and the emphasis on main points and ideas, is accurately transmitted.
+5. The guidance on using abbreviations and the example given about King Henry VIII are accurate, but there's a slight variation in the abbreviation for King Henry VIII ("H8" vs. "H," and the clarification regarding confusion with King Henry VII).
+6. The segment about knowing your professor and adapting note-taking methods is correctly reflected, including the specific mention of the Cornell style and visual learners.
+7. The suggestion to show notes to the professor for feedback is accurately conveyed.
+
+**Errors Identified:**
+
+1. Name: "Anng" vs. "Anna"
+2. Abbreviation for King Henry VIII: "H8" vs. "H" (and the subsequent clarification regarding confusion with King Henry VII is slightly different but conveys the same idea).
+3. Minor punctuation and formatting differences (e.g., use of parentheses and commas).
+
+**Assessment:**
+
+- The main body of the text is highly accurate, with the essence of the study tips and note-taking strategies correctly captured.
+- There's a minor error in the name ("Anng" vs. "Anna").
+- The minor differences in the abbreviation for King Henry VIII do not change the overall meaning or utility of the advice given.
+- Punctuation and formatting discrepancies are minor and do not substantially impact the readability or the conveyance of information.
+
+Given the high level of detail and fidelity to the original content, with only minor discrepancies, we can estimate the accuracy to be very high. For a numerical estimation, assuming a simple model where each identified issue (name error, slight abbreviation difference, minor punctuation/formatting) counts as a minor error, and given the overall length and complexity of the text:
+
+- If we were to assign a numerical value, assuming approximately 3 minor errors in a text of this detail and length, the accuracy might be estimated at approximately 98-99%. This is because the errors are very minor in the context of the entire text, and the core content is accurately preserved and conveyed.
+
+This is a subjective assessment, as the exact scoring can depend on the specific criteria and weight assigned to different types of errors (e.g., content vs. formatting).
diff --git a/Large Language Model/LLM - Kareem/LLM v3/main.py b/models/Large Language Model/LLM - Kareem/LLM v3/main.py
similarity index 97%
rename from Large Language Model/LLM - Kareem/LLM v3/main.py
rename to models/Large Language Model/LLM - Kareem/LLM v3/main.py
index 1113b29..befb3e0 100644
--- a/Large Language Model/LLM - Kareem/LLM v3/main.py
+++ b/models/Large Language Model/LLM - Kareem/LLM v3/main.py
@@ -1,88 +1,88 @@
-import os
-from openai import OpenAI
-from dotenv import load_dotenv, find_dotenv
-
-def main():
- # Load the .env file
- _ = load_dotenv(find_dotenv())
- client = OpenAI(
- api_key = os.environ.get("OPENAI_API_KEY")
- )
-
- model = "gpt-4-turbo-preview"
- temperature = 0.7
-
- # Read input file
- try:
- with open('input.txt', 'r') as file:
- input_file = file.read()
- except FileNotFoundError:
- print("File not found. Please check the file name or path.")
- exit(1)
-
- while True:
- user_message = input(f"\n===========================================================\
- \n(1) Correct the spelling and grammar errors of the document \
- \n(2) Reformat the document \
- \n(3) Correct spelling/grammar and reformat the document \
- \n(4) Print the current version of the document \
- \n(5) Produce accuracy score \
- \n(6) Quit \
- \n\nPlease select an action to perform on the document: ")
- print("\n")
-
- if user_message == "1": # spelling and grammar correction
- prompt = "Without reformatting the document, this includes creating new lines or altering the spacing in any way and keeping the characters where they are, fix the spelling errors of the following document"
-
- elif user_message == "2": # reformat the document
- prompt = "Reformat input so that it displays the text in its intended format."
-
- elif user_message == "3": # correct spelling/grammar and reformat the document
- prompt = "Fix the spelling and grammar errors of the following document and reformat the document so that it displays the text in its intended format. Print only the result without any additional text or responses."
-
- elif user_message == "4": # print the current version of the document
- output_file = "output.txt"
- try:
- with open(output_file, "r") as file:
- file_content = file.read()
- except IOError: # unable to open file
- print("Error: File Not Found")
- print("--------------------------------------------------------------------------\n"
- + file_content
- + "\n--------------------------------------------------------------------------\n")
- continue
-
- elif user_message == "5": # produce accuracy score
- prompt = "The following file contains two parts. The first part is the output from a computer vision program after corrections, and the second part is the original text. Please produce an accuracy score accessing how accurate the computer vision output is to the orignal text."
-
- elif user_message == "6": # Quit
- print("Exiting the program...")
- break
-
- # Prepare the API request with the file contents and selected prompt
- messages =[
- {"role": "system", "content": input_file},
- {"role": "user", "content": prompt},
- ]
- writeToOutput(client, model, messages, temperature)
-
-# Make API request
-def get_summary(client, model, messages, temperature):
- completion = client.chat.completions.create(
- model=model,
- messages=messages,
- temperature=temperature,
- )
- return completion.choices[0].message.content
-
-# Write changes to output file
-def writeToOutput(client, model, messages, temperature):
- output_file = "output.txt"
- try:
- with open(output_file, "w") as file:
- file.write(get_summary(client, model, messages, temperature))
- except IOError: # unable to open file
- print("Error: Unable to write to the file.")
-
-if __name__ == "__main__":
- main()
+import os
+from openai import OpenAI
+from dotenv import load_dotenv, find_dotenv
+
+def main():
+ # Load the .env file
+ _ = load_dotenv(find_dotenv())
+ client = OpenAI(
+ api_key = os.environ.get("OPENAI_API_KEY")
+ )
+
+ model = "gpt-4-turbo-preview"
+ temperature = 0.7
+
+ # Read input file
+ try:
+ with open('input.txt', 'r') as file:
+ input_file = file.read()
+ except FileNotFoundError:
+ print("File not found. Please check the file name or path.")
+ exit(1)
+
+ while True:
+ user_message = input(f"\n===========================================================\
+ \n(1) Correct the spelling and grammar errors of the document \
+ \n(2) Reformat the document \
+ \n(3) Correct spelling/grammar and reformat the document \
+ \n(4) Print the current version of the document \
+ \n(5) Produce accuracy score \
+ \n(6) Quit \
+ \n\nPlease select an action to perform on the document: ")
+ print("\n")
+
+ if user_message == "1": # spelling and grammar correction
+ prompt = "Without reformatting the document, this includes creating new lines or altering the spacing in any way and keeping the characters where they are, fix the spelling errors of the following document"
+
+ elif user_message == "2": # reformat the document
+ prompt = "Reformat input so that it displays the text in its intended format."
+
+ elif user_message == "3": # correct spelling/grammar and reformat the document
+ prompt = "Fix the spelling and grammar errors of the following document and reformat the document so that it displays the text in its intended format. Print only the result without any additional text or responses."
+
+ elif user_message == "4": # print the current version of the document
+ output_file = "output.txt"
+ try:
+ with open(output_file, "r") as file:
+ file_content = file.read()
+ except IOError: # unable to open file
+ print("Error: File Not Found")
+ print("--------------------------------------------------------------------------\n"
+ + file_content
+ + "\n--------------------------------------------------------------------------\n")
+ continue
+
+ elif user_message == "5": # produce accuracy score
+ prompt = "The following file contains two parts. The first part is the output from a computer vision program after corrections, and the second part is the original text. Please produce an accuracy score accessing how accurate the computer vision output is to the orignal text."
+
+ elif user_message == "6": # Quit
+ print("Exiting the program...")
+ break
+
+ # Prepare the API request with the file contents and selected prompt
+ messages =[
+ {"role": "system", "content": input_file},
+ {"role": "user", "content": prompt},
+ ]
+ writeToOutput(client, model, messages, temperature)
+
+# Make API request
+def get_summary(client, model, messages, temperature):
+ completion = client.chat.completions.create(
+ model=model,
+ messages=messages,
+ temperature=temperature,
+ )
+ return completion.choices[0].message.content
+
+# Write changes to output file
+def writeToOutput(client, model, messages, temperature):
+ output_file = "output.txt"
+ try:
+ with open(output_file, "w") as file:
+ file.write(get_summary(client, model, messages, temperature))
+ except IOError: # unable to open file
+ print("Error: Unable to write to the file.")
+
+if __name__ == "__main__":
+ main()
diff --git a/Large Language Model/LLM - Kareem/LLM v3/reformatted_output.txt b/models/Large Language Model/LLM - Kareem/LLM v3/reformatted_output.txt
similarity index 98%
rename from Large Language Model/LLM - Kareem/LLM v3/reformatted_output.txt
rename to models/Large Language Model/LLM - Kareem/LLM v3/reformatted_output.txt
index 574bb30..e479f13 100644
--- a/Large Language Model/LLM - Kareem/LLM v3/reformatted_output.txt
+++ b/models/Large Language Model/LLM - Kareem/LLM v3/reformatted_output.txt
@@ -1,21 +1,21 @@
-**Study Tips**
-
-**Prepare Before Class:**
-- Review notes from the last class.
-- Look through the chapter to understand the basic idea of this class.
-- May take 20-30 mins but makes a huge difference.
-
-**When in Doubt, Write it Down:**
-- Don't waste time deciding if you should write something down.
-- Focus on main points and ideas.
-- Don't write everything. Leave out unnecessary words.
-
-**Think About Your Notes:**
-- Use abbreviations and include a key for lecture-specific abbreviations.
- - For example, in a lecture about King Henry VIII, instead of writing his name out each time, make a note such as King Henry VIII = H8. If this could be confused with King Henry VII, abbreviate it as King Henry VIII = 8.
-
-**Know Your Professor:**
-- Use the note-taking method that will best help you with this particular professor's lectures.
- - A more organized professor works well with the Cornell style.
- - Use the note-taking method for visual learners for a professor that jumps around and returns to previous points.
+**Study Tips**
+
+**Prepare Before Class:**
+- Review notes from the last class.
+- Look through the chapter to understand the basic idea of this class.
+- May take 20-30 mins but makes a huge difference.
+
+**When in Doubt, Write it Down:**
+- Don't waste time deciding if you should write something down.
+- Focus on main points and ideas.
+- Don't write everything. Leave out unnecessary words.
+
+**Think About Your Notes:**
+- Use abbreviations and include a key for lecture-specific abbreviations.
+ - For example, in a lecture about King Henry VIII, instead of writing his name out each time, make a note such as King Henry VIII = H8. If this could be confused with King Henry VII, abbreviate it as King Henry VIII = 8.
+
+**Know Your Professor:**
+- Use the note-taking method that will best help you with this particular professor's lectures.
+ - A more organized professor works well with the Cornell style.
+ - Use the note-taking method for visual learners for a professor that jumps around and returns to previous points.
- Show the professor your notes after the first lecture and ask if you are hitting the main points.
\ No newline at end of file
diff --git a/Large Language Model/LLM - Kareem/LLM v3/unformatted_input.txt b/models/Large Language Model/LLM - Kareem/LLM v3/unformatted_input.txt
similarity index 97%
rename from Large Language Model/LLM - Kareem/LLM v3/unformatted_input.txt
rename to models/Large Language Model/LLM - Kareem/LLM v3/unformatted_input.txt
index c972542..452a9e1 100644
--- a/Large Language Model/LLM - Kareem/LLM v3/unformatted_input.txt
+++ b/models/Large Language Model/LLM - Kareem/LLM v3/unformatted_input.txt
@@ -1,31 +1,31 @@
-Basic Notes
-Anng
-10/9/12
-Study Tips
-Prepare before class
-review notes from last class
-look through chapter to understand basic idea of
-this class
-May take 20-30 mins but makes a huge difference
-When in doubt, write it down
-Point wste everything though Main Points Main Ideas
-Don't spend too long deciding If you should
-Write down
-Think your notes
-Don't write everything Leave out words
-use abbreviations-include a key of specific-for-
-this lecture abbreviations. e.g. A lecture about
-King Henry VIII will require you to repeat his name
-several times. instead of writing it out each
-Time make a note King Henry VIII=H8 if that
-would be confused with King Henry VII abbreviate
-it as King Henry VIII=8
-know your prof
-use the note-taking method that will best help
-you with this particular prof's lectures
-A more organized prof works well with the
-Cornell style. use the note-taking method
-for visual learners for a prof that jumps
-around and returns to previous points
-Show the prof your notes after the first
+Basic Notes
+Anng
+10/9/12
+Study Tips
+Prepare before class
+review notes from last class
+look through chapter to understand basic idea of
+this class
+May take 20-30 mins but makes a huge difference
+When in doubt, write it down
+Point wste everything though Main Points Main Ideas
+Don't spend too long deciding If you should
+Write down
+Think your notes
+Don't write everything Leave out words
+use abbreviations-include a key of specific-for-
+this lecture abbreviations. e.g. A lecture about
+King Henry VIII will require you to repeat his name
+several times. instead of writing it out each
+Time make a note King Henry VIII=H8 if that
+would be confused with King Henry VII abbreviate
+it as King Henry VIII=8
+know your prof
+use the note-taking method that will best help
+you with this particular prof's lectures
+A more organized prof works well with the
+Cornell style. use the note-taking method
+for visual learners for a prof that jumps
+around and returns to previous points
+Show the prof your notes after the first
lecture and ask if you are hitting main points.
\ No newline at end of file
diff --git a/Large Language Model/Large Language Models Presentation (LLMs).pdf b/models/Large Language Model/Large Language Models Presentation (LLMs).pdf
similarity index 100%
rename from Large Language Model/Large Language Models Presentation (LLMs).pdf
rename to models/Large Language Model/Large Language Models Presentation (LLMs).pdf
diff --git a/Large Language Model/Final LLM Model/reformatted_output.txt b/models/Natural Language Processing Model/LLM_preformatted.txt
similarity index 98%
rename from Large Language Model/Final LLM Model/reformatted_output.txt
rename to models/Natural Language Processing Model/LLM_preformatted.txt
index 574bb30..e479f13 100644
--- a/Large Language Model/Final LLM Model/reformatted_output.txt
+++ b/models/Natural Language Processing Model/LLM_preformatted.txt
@@ -1,21 +1,21 @@
-**Study Tips**
-
-**Prepare Before Class:**
-- Review notes from the last class.
-- Look through the chapter to understand the basic idea of this class.
-- May take 20-30 mins but makes a huge difference.
-
-**When in Doubt, Write it Down:**
-- Don't waste time deciding if you should write something down.
-- Focus on main points and ideas.
-- Don't write everything. Leave out unnecessary words.
-
-**Think About Your Notes:**
-- Use abbreviations and include a key for lecture-specific abbreviations.
- - For example, in a lecture about King Henry VIII, instead of writing his name out each time, make a note such as King Henry VIII = H8. If this could be confused with King Henry VII, abbreviate it as King Henry VIII = 8.
-
-**Know Your Professor:**
-- Use the note-taking method that will best help you with this particular professor's lectures.
- - A more organized professor works well with the Cornell style.
- - Use the note-taking method for visual learners for a professor that jumps around and returns to previous points.
+**Study Tips**
+
+**Prepare Before Class:**
+- Review notes from the last class.
+- Look through the chapter to understand the basic idea of this class.
+- May take 20-30 mins but makes a huge difference.
+
+**When in Doubt, Write it Down:**
+- Don't waste time deciding if you should write something down.
+- Focus on main points and ideas.
+- Don't write everything. Leave out unnecessary words.
+
+**Think About Your Notes:**
+- Use abbreviations and include a key for lecture-specific abbreviations.
+ - For example, in a lecture about King Henry VIII, instead of writing his name out each time, make a note such as King Henry VIII = H8. If this could be confused with King Henry VII, abbreviate it as King Henry VIII = 8.
+
+**Know Your Professor:**
+- Use the note-taking method that will best help you with this particular professor's lectures.
+ - A more organized professor works well with the Cornell style.
+ - Use the note-taking method for visual learners for a professor that jumps around and returns to previous points.
- Show the professor your notes after the first lecture and ask if you are hitting the main points.
\ No newline at end of file
diff --git a/Natural Language Processing Model/NLP Presentation-Format.pptx b/models/Natural Language Processing Model/NLP Presentation-Format.pptx
similarity index 100%
rename from Natural Language Processing Model/NLP Presentation-Format.pptx
rename to models/Natural Language Processing Model/NLP Presentation-Format.pptx
diff --git a/models/Natural Language Processing Model/NLP Presentation.pptx b/models/Natural Language Processing Model/NLP Presentation.pptx
new file mode 100644
index 0000000..e69de29
diff --git a/Natural Language Processing Model/NLPModel.ipynb b/models/Natural Language Processing Model/NLPModel.ipynb
similarity index 100%
rename from Natural Language Processing Model/NLPModel.ipynb
rename to models/Natural Language Processing Model/NLPModel.ipynb
diff --git a/Natural Language Processing Model/NLPModel_1.py b/models/Natural Language Processing Model/NLPModel_1.py
similarity index 100%
rename from Natural Language Processing Model/NLPModel_1.py
rename to models/Natural Language Processing Model/NLPModel_1.py
diff --git a/Natural Language Processing Model/NLPModel_2.py b/models/Natural Language Processing Model/NLPModel_2.py
similarity index 100%
rename from Natural Language Processing Model/NLPModel_2.py
rename to models/Natural Language Processing Model/NLPModel_2.py
diff --git a/Natural Language Processing Model/NLPModel_3.py b/models/Natural Language Processing Model/NLPModel_3.py
similarity index 100%
rename from Natural Language Processing Model/NLPModel_3.py
rename to models/Natural Language Processing Model/NLPModel_3.py
diff --git a/Natural Language Processing Model/NLPModel_4.py b/models/Natural Language Processing Model/NLPModel_4.py
similarity index 100%
rename from Natural Language Processing Model/NLPModel_4.py
rename to models/Natural Language Processing Model/NLPModel_4.py
diff --git a/Natural Language Processing Model/__pycache__/NLPModel_2.cpython-312.pyc b/models/Natural Language Processing Model/__pycache__/NLPModel_2.cpython-312.pyc
similarity index 100%
rename from Natural Language Processing Model/__pycache__/NLPModel_2.cpython-312.pyc
rename to models/Natural Language Processing Model/__pycache__/NLPModel_2.cpython-312.pyc
diff --git a/Natural Language Processing Model/__pycache__/NLPModel_3.cpython-311.pyc b/models/Natural Language Processing Model/__pycache__/NLPModel_3.cpython-311.pyc
similarity index 100%
rename from Natural Language Processing Model/__pycache__/NLPModel_3.cpython-311.pyc
rename to models/Natural Language Processing Model/__pycache__/NLPModel_3.cpython-311.pyc
diff --git a/Natural Language Processing Model/__pycache__/NLPModel_3.cpython-312.pyc b/models/Natural Language Processing Model/__pycache__/NLPModel_3.cpython-312.pyc
similarity index 100%
rename from Natural Language Processing Model/__pycache__/NLPModel_3.cpython-312.pyc
rename to models/Natural Language Processing Model/__pycache__/NLPModel_3.cpython-312.pyc
diff --git a/Natural Language Processing Model/__pycache__/NLPModel_4.cpython-311.pyc b/models/Natural Language Processing Model/__pycache__/NLPModel_4.cpython-311.pyc
similarity index 100%
rename from Natural Language Processing Model/__pycache__/NLPModel_4.cpython-311.pyc
rename to models/Natural Language Processing Model/__pycache__/NLPModel_4.cpython-311.pyc
diff --git a/Natural Language Processing Model/output.html b/models/Natural Language Processing Model/output.html
similarity index 100%
rename from Natural Language Processing Model/output.html
rename to models/Natural Language Processing Model/output.html
diff --git a/Natural Language Processing Model/output.pdf b/models/Natural Language Processing Model/output.pdf
similarity index 100%
rename from Natural Language Processing Model/output.pdf
rename to models/Natural Language Processing Model/output.pdf
diff --git a/Natural Language Processing Model/testingNLPModel.py b/models/Natural Language Processing Model/testingNLPModel.py
similarity index 100%
rename from Natural Language Processing Model/testingNLPModel.py
rename to models/Natural Language Processing Model/testingNLPModel.py
diff --git a/Natural Language Processing Model/transformed_text.txt b/models/Natural Language Processing Model/transformed_text.txt
similarity index 100%
rename from Natural Language Processing Model/transformed_text.txt
rename to models/Natural Language Processing Model/transformed_text.txt
diff --git a/Presentation Slides/InkWave Charts.drawio.pdf b/models/Presentation Slides/InkWave Charts.drawio.pdf
similarity index 100%
rename from Presentation Slides/InkWave Charts.drawio.pdf
rename to models/Presentation Slides/InkWave Charts.drawio.pdf
diff --git a/Presentation Slides/InkWave Sprint 1.pptx b/models/Presentation Slides/InkWave Sprint 1.pptx
similarity index 100%
rename from Presentation Slides/InkWave Sprint 1.pptx
rename to models/Presentation Slides/InkWave Sprint 1.pptx
diff --git a/Presentation Slides/InkWave Sprint 2.pptx b/models/Presentation Slides/InkWave Sprint 2.pptx
similarity index 100%
rename from Presentation Slides/InkWave Sprint 2.pptx
rename to models/Presentation Slides/InkWave Sprint 2.pptx
diff --git a/Presentation Slides/InkWave Sprint 3.pptx b/models/Presentation Slides/InkWave Sprint 3.pptx
similarity index 100%
rename from Presentation Slides/InkWave Sprint 3.pptx
rename to models/Presentation Slides/InkWave Sprint 3.pptx
diff --git a/Presentation Slides/InkWave Sprint 4.pptx b/models/Presentation Slides/InkWave Sprint 4.pptx
similarity index 100%
rename from Presentation Slides/InkWave Sprint 4.pptx
rename to models/Presentation Slides/InkWave Sprint 4.pptx
diff --git a/Presentation Slides/InkWave Sprint 5 Slides.pptx b/models/Presentation Slides/InkWave Sprint 5 Slides.pptx
similarity index 100%
rename from Presentation Slides/InkWave Sprint 5 Slides.pptx
rename to models/Presentation Slides/InkWave Sprint 5 Slides.pptx
diff --git a/models/cvModel/PAddleVsTesseract.py b/models/cvModel/PAddleVsTesseract.py
new file mode 100644
index 0000000..4465807
--- /dev/null
+++ b/models/cvModel/PAddleVsTesseract.py
@@ -0,0 +1,108 @@
+import cv2
+import time
+import pytesseract
+from paddleocr import PaddleOCR
+from difflib import unified_diff
+
+class OCRProcessor:
+ def __init__(self, image_path, output_path='ocr_output.txt', language='en', engine='paddle'):
+ self.image_path = image_path
+ self.output_path = output_path
+ self.language = language
+ self.engine = engine.lower()
+
+ def load_and_preprocess_image(self):
+ image = cv2.imread(self.image_path)
+ if image is None:
+ raise FileNotFoundError(f"Image not found: {self.image_path}")
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
+ _, thresh = cv2.threshold(gray, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
+ return thresh
+
+ def extract_text(self, image):
+ if self.engine == 'tesseract':
+ return pytesseract.image_to_string(image, lang=self.language)
+ elif self.engine == 'paddle':
+ ocr = PaddleOCR(use_angle_cls=True, lang=self.language, show_log=False)
+ result = ocr.ocr(image, cls=True)
+ return "\n".join([line[1][0] for box in result for line in box])
+ else:
+ raise ValueError(f"Unsupported OCR engine: {self.engine}")
+
+ def save_text_to_file(self, text):
+ with open(self.output_path, 'w', encoding='utf-8') as f:
+ f.write(text)
+
+ def process_image(self):
+ image = self.load_and_preprocess_image()
+ text = self.extract_text(image)
+ self.save_text_to_file(text)
+ return text
+
+
+class OCRComparator:
+ def __init__(self, image_path='input_image.jpg', language='en'):
+ self.image_path = image_path
+ self.language = language
+
+ def compare_engines(self):
+ results = {}
+
+ # PaddleOCR
+ paddle_processor = OCRProcessor(
+ image_path=self.image_path,
+ output_path='output_paddle.txt',
+ language=self.language,
+ engine='paddle'
+ )
+ start = time.time()
+ paddle_text = paddle_processor.process_image()
+ paddle_time = time.time() - start
+
+ results['paddle'] = {
+ 'time': paddle_time,
+ 'text': paddle_text.splitlines(keepends=True)
+ }
+
+ # Tesseract OCR
+ tesseract_processor = OCRProcessor(
+ image_path=self.image_path,
+ output_path='output_tesseract.txt',
+ language=self.language,
+ engine='tesseract'
+ )
+ start = time.time()
+ tesseract_text = tesseract_processor.process_image()
+ tesseract_time = time.time() - start
+
+ results['tesseract'] = {
+ 'time': tesseract_time,
+ 'text': tesseract_text.splitlines(keepends=True)
+ }
+
+ self.show_results(results)
+
+ def show_results(self, results):
+ print("\n=== PaddleOCR Output ===")
+ print("Time taken: {:.2f}s".format(results['paddle']['time']))
+ print("".join(results['paddle']['text']))
+
+ print("\n=== Tesseract Output ===")
+ print("Time taken: {:.2f}s".format(results['tesseract']['time']))
+ print("".join(results['tesseract']['text']))
+
+ print("\n=== Difference (Paddle vs. Tesseract) ===")
+ diff = unified_diff(
+ results['paddle']['text'],
+ results['tesseract']['text'],
+ fromfile='PaddleOCR',
+ tofile='TesseractOCR',
+ lineterm=''
+ )
+ for line in diff:
+ print(line)
+
+
+if __name__ == '__main__':
+ comparator = OCRComparator(image_path='test1.jpg', language='en')
+ comparator.compare_engines()
diff --git a/cvModel/accuracytest.py b/models/cvModel/accuracytest.py
similarity index 97%
rename from cvModel/accuracytest.py
rename to models/cvModel/accuracytest.py
index f2b6b50..44523ea 100644
--- a/cvModel/accuracytest.py
+++ b/models/cvModel/accuracytest.py
@@ -1,177 +1,177 @@
-import os
-from paddleocr import PaddleOCR
-import csv
-
-# Initialize PaddleOCR with the local model directory
-local_model_dir = './local_paddleocr_model/'
-ocr = PaddleOCR(use_angle_cls=True, lang='en', rec_model_dir=f'{local_model_dir}/rec')
-
-# Paths to the directories
-img_dir = './validation_v2/validation/'
-answer_key_path = 'written_name_validation_v2.csv'
-output_path = './output.txt'
-
-# Read the answer key CSV file
-answer_key = {}
-try:
- with open(answer_key_path, newline='', encoding='utf-8') as csvfile:
- reader = csv.reader(csvfile)
- for row in reader:
- if len(row) > 1:
- answer_key[row[0]] = row[1]
-except PermissionError:
- print(f"Permission denied: '{answer_key_path}'. Please check file permissions.")
- exit(1)
-except FileNotFoundError:
- print(f"File not found: '{answer_key_path}'. Please check the file path.")
- exit(1)
-
-print(answer_key)
-
-# Initialize statistics
-total_images = 0
-contained_count = 0
-not_contained_count = 0
-not_found_count = 0
-exact_match_count = 0
-total_characters = 0
-correct_characters = 0
-
-def longest_common_subsequence(X, Y):
- m = len(X)
- n = len(Y)
- L = [[0] * (n + 1) for _ in range(m + 1)]
-
- for i in range(m + 1):
- for j in range(n + 1):
- if i == 0 or j == 0:
- L[i][j] = 0
- elif X[i - 1] == Y[j - 1]:
- L[i][j] = L[i - 1][j - 1] + 1
- else:
- L[i][j] = max(L[i - 1][j], L[i][j - 1])
-
- return L[m][n]
-
-# Process the first 1000 images listed in the CSV file
-with open(output_path, 'w') as output_file:
- for idx, (img_filename, answer_text) in enumerate(answer_key.items()):
-
- if idx >= 100:
- break
-
- total_images += 1
- img_path = os.path.join(img_dir, img_filename)
- print(img_path)
-
- if not os.path.exists(img_path):
- not_found_count += 1
- output_file.write(f"Image: {img_filename}\n")
- output_file.write("Extracted: File not found\n")
- output_file.write(f"Answer: {answer_text}\n")
- output_file.write("Contained: False\n")
- output_file.write("\n")
- print(f"Image: {img_filename}")
- print("Extracted: File not found")
- print(f"Answer: {answer_text}")
- print("Contained: False")
- print("\n")
- not_contained_count += 1
- continue
-
- result = ocr.ocr(img_path, cls=True)
-
- # Handle case where result is None
- if result is None:
- output_file.write(f"Image: {img_filename}\n")
- output_file.write("Extracted: No text detected\n")
- output_file.write(f"Answer: {answer_text}\n")
- output_file.write("Contained: False\n")
- output_file.write("\n")
- print(f"Image: {img_filename}")
- print("Extracted: No text detected")
- print(f"Answer: {answer_text}")
- print("Contained: False")
- print("\n")
- not_contained_count += 1
- continue
-
- # Extract text from the OCR result
- extracted_text = []
- for res in result:
- if res is not None:
- for line in res:
- extracted_text.append(line[1][0])
-
- # Join extracted text for comparison
- extracted_text_str = " ".join(extracted_text).lower()
- answer_text_lower = answer_text.lower()
-
- # Check if the answer text is contained in the extracted text
- is_contained = answer_text_lower in extracted_text_str
-
- # Check if the extracted text is exactly the same as the answer text
- is_exact_match = extracted_text_str == answer_text_lower
- if is_exact_match:
- exact_match_count += 1
-
- # Update statistics
- if is_contained:
- contained_count += 1
- else:
- not_contained_count += 1
-
- # Calculate character-level correctness using LCS
- correct_char_count = longest_common_subsequence(answer_text_lower, extracted_text_str)
- total_characters += len(answer_text_lower)
- correct_characters += correct_char_count
-
- # Write to the output file
- output_file.write(f"Image: {img_filename}\n")
- output_file.write(f"Extracted: {extracted_text_str}\n")
- output_file.write(f"Answer: {answer_text}\n")
- output_file.write(f"Contained: {is_contained}\n")
- output_file.write(f"Exact Match: {is_exact_match}\n")
- output_file.write(f"Correct Characters: {correct_char_count} / {len(answer_text_lower)}\n")
- output_file.write("\n")
-
- # Print for console output (optional)
- print(f"Image: {img_filename}")
- print(f"Extracted: {extracted_text_str}")
- print(f"Answer: {answer_text}")
- print(f"Contained: {is_contained}")
- print(f"Exact Match: {is_exact_match}")
- print(f"Correct Characters: {correct_char_count} / {len(answer_text_lower)}")
- print("\n")
-
- # Calculate additional statistics
- if total_images > 0:
- percentage_contained = (contained_count / total_images) * 100
- percentage_not_contained = (not_contained_count / total_images) * 100
- character_accuracy = (correct_characters / total_characters) * 100
- else:
- percentage_contained = 0
- percentage_not_contained = 0
- character_accuracy = 0
-
- # Write statistics to the output file
- output_file.write("Statistics:\n")
- output_file.write(f"Total images processed: {total_images}\n")
- output_file.write(f"Images where answer text is contained: {contained_count}\n")
- output_file.write(f"Images where answer text is not contained: {not_contained_count}\n")
- output_file.write(f"Files not found: {not_found_count}\n")
- output_file.write(f"Percentage contained: {percentage_contained:.2f}%\n")
- output_file.write(f"Percentage not contained: {percentage_not_contained:.2f}%\n")
- output_file.write(f"Exact match count: {exact_match_count}\n")
- output_file.write(f"Character-level accuracy: {character_accuracy:.2f}%\n")
-
- # Print statistics for console output (optional)
- print("Statistics:")
- print(f"Total images processed: {total_images}")
- print(f"Images where answer text is contained: {contained_count}")
- print(f"Images where answer text is not contained: {not_contained_count}")
- print(f"Files not found: {not_found_count}")
- print(f"Percentage contained: {percentage_contained:.2f}%")
- print(f"Percentage not contained: {percentage_not_contained:.2f}%")
- print(f"Exact match count: {exact_match_count}")
- print(f"Character-level accuracy: {character_accuracy:.2f}%")
+import os
+from paddleocr import PaddleOCR
+import csv
+
+# Initialize PaddleOCR with the local model directory
+local_model_dir = './local_paddleocr_model/'
+ocr = PaddleOCR(use_angle_cls=True, lang='en', rec_model_dir=f'{local_model_dir}/rec')
+
+# Paths to the directories
+img_dir = './validation_v2/validation/'
+answer_key_path = 'written_name_validation_v2.csv'
+output_path = './output.txt'
+
+# Read the answer key CSV file
+answer_key = {}
+try:
+ with open(answer_key_path, newline='', encoding='utf-8') as csvfile:
+ reader = csv.reader(csvfile)
+ for row in reader:
+ if len(row) > 1:
+ answer_key[row[0]] = row[1]
+except PermissionError:
+ print(f"Permission denied: '{answer_key_path}'. Please check file permissions.")
+ exit(1)
+except FileNotFoundError:
+ print(f"File not found: '{answer_key_path}'. Please check the file path.")
+ exit(1)
+
+print(answer_key)
+
+# Initialize statistics
+total_images = 0
+contained_count = 0
+not_contained_count = 0
+not_found_count = 0
+exact_match_count = 0
+total_characters = 0
+correct_characters = 0
+
+def longest_common_subsequence(X, Y):
+ m = len(X)
+ n = len(Y)
+ L = [[0] * (n + 1) for _ in range(m + 1)]
+
+ for i in range(m + 1):
+ for j in range(n + 1):
+ if i == 0 or j == 0:
+ L[i][j] = 0
+ elif X[i - 1] == Y[j - 1]:
+ L[i][j] = L[i - 1][j - 1] + 1
+ else:
+ L[i][j] = max(L[i - 1][j], L[i][j - 1])
+
+ return L[m][n]
+
+# Process the first 1000 images listed in the CSV file
+with open(output_path, 'w') as output_file:
+ for idx, (img_filename, answer_text) in enumerate(answer_key.items()):
+
+ if idx >= 100:
+ break
+
+ total_images += 1
+ img_path = os.path.join(img_dir, img_filename)
+ print(img_path)
+
+ if not os.path.exists(img_path):
+ not_found_count += 1
+ output_file.write(f"Image: {img_filename}\n")
+ output_file.write("Extracted: File not found\n")
+ output_file.write(f"Answer: {answer_text}\n")
+ output_file.write("Contained: False\n")
+ output_file.write("\n")
+ print(f"Image: {img_filename}")
+ print("Extracted: File not found")
+ print(f"Answer: {answer_text}")
+ print("Contained: False")
+ print("\n")
+ not_contained_count += 1
+ continue
+
+ result = ocr.ocr(img_path, cls=True)
+
+ # Handle case where result is None
+ if result is None:
+ output_file.write(f"Image: {img_filename}\n")
+ output_file.write("Extracted: No text detected\n")
+ output_file.write(f"Answer: {answer_text}\n")
+ output_file.write("Contained: False\n")
+ output_file.write("\n")
+ print(f"Image: {img_filename}")
+ print("Extracted: No text detected")
+ print(f"Answer: {answer_text}")
+ print("Contained: False")
+ print("\n")
+ not_contained_count += 1
+ continue
+
+ # Extract text from the OCR result
+ extracted_text = []
+ for res in result:
+ if res is not None:
+ for line in res:
+ extracted_text.append(line[1][0])
+
+ # Join extracted text for comparison
+ extracted_text_str = " ".join(extracted_text).lower()
+ answer_text_lower = answer_text.lower()
+
+ # Check if the answer text is contained in the extracted text
+ is_contained = answer_text_lower in extracted_text_str
+
+ # Check if the extracted text is exactly the same as the answer text
+ is_exact_match = extracted_text_str == answer_text_lower
+ if is_exact_match:
+ exact_match_count += 1
+
+ # Update statistics
+ if is_contained:
+ contained_count += 1
+ else:
+ not_contained_count += 1
+
+ # Calculate character-level correctness using LCS
+ correct_char_count = longest_common_subsequence(answer_text_lower, extracted_text_str)
+ total_characters += len(answer_text_lower)
+ correct_characters += correct_char_count
+
+ # Write to the output file
+ output_file.write(f"Image: {img_filename}\n")
+ output_file.write(f"Extracted: {extracted_text_str}\n")
+ output_file.write(f"Answer: {answer_text}\n")
+ output_file.write(f"Contained: {is_contained}\n")
+ output_file.write(f"Exact Match: {is_exact_match}\n")
+ output_file.write(f"Correct Characters: {correct_char_count} / {len(answer_text_lower)}\n")
+ output_file.write("\n")
+
+ # Print for console output (optional)
+ print(f"Image: {img_filename}")
+ print(f"Extracted: {extracted_text_str}")
+ print(f"Answer: {answer_text}")
+ print(f"Contained: {is_contained}")
+ print(f"Exact Match: {is_exact_match}")
+ print(f"Correct Characters: {correct_char_count} / {len(answer_text_lower)}")
+ print("\n")
+
+ # Calculate additional statistics
+ if total_images > 0:
+ percentage_contained = (contained_count / total_images) * 100
+ percentage_not_contained = (not_contained_count / total_images) * 100
+ character_accuracy = (correct_characters / total_characters) * 100
+ else:
+ percentage_contained = 0
+ percentage_not_contained = 0
+ character_accuracy = 0
+
+ # Write statistics to the output file
+ output_file.write("Statistics:\n")
+ output_file.write(f"Total images processed: {total_images}\n")
+ output_file.write(f"Images where answer text is contained: {contained_count}\n")
+ output_file.write(f"Images where answer text is not contained: {not_contained_count}\n")
+ output_file.write(f"Files not found: {not_found_count}\n")
+ output_file.write(f"Percentage contained: {percentage_contained:.2f}%\n")
+ output_file.write(f"Percentage not contained: {percentage_not_contained:.2f}%\n")
+ output_file.write(f"Exact match count: {exact_match_count}\n")
+ output_file.write(f"Character-level accuracy: {character_accuracy:.2f}%\n")
+
+ # Print statistics for console output (optional)
+ print("Statistics:")
+ print(f"Total images processed: {total_images}")
+ print(f"Images where answer text is contained: {contained_count}")
+ print(f"Images where answer text is not contained: {not_contained_count}")
+ print(f"Files not found: {not_found_count}")
+ print(f"Percentage contained: {percentage_contained:.2f}%")
+ print(f"Percentage not contained: {percentage_not_contained:.2f}%")
+ print(f"Exact match count: {exact_match_count}")
+ print(f"Character-level accuracy: {character_accuracy:.2f}%")
diff --git a/models/cvModel/fine_tune.py b/models/cvModel/fine_tune.py
new file mode 100644
index 0000000..6e238d6
--- /dev/null
+++ b/models/cvModel/fine_tune.py
@@ -0,0 +1,59 @@
+from paddleocr import PaddleOCR
+import cv2
+import os
+import numpy as np
+
+# Specify the directory of your fine-tuned model and the folder with images
+rec_model_dir = "new_model.h5"
+img_folder_path = "C:\\Users\\prern\\OneDrive\\Desktop\\Codes\\OLD codes\\CPP\\Spring24 Projects\\InkWave\\dataset\\Dataset\\FinalDataSetForCV"
+output_file_path = "./output.txt"
+
+# Initialize PaddleOCR with your fine-tuned model
+ocr = PaddleOCR(rec_model_dir=rec_model_dir, use_angle_cls=True, lang='en')
+
+def preprocess_image(image_path):
+ """
+ Preprocess the image for better OCR results.
+ - Converts to grayscale
+ - Applies adaptive thresholding
+ - Removes noise using morphological operations
+ """
+ image = cv2.imread(image_path)
+ if image is None:
+ raise ValueError(f"Could not read image: {image_path}")
+
+ gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
+ thresh = cv2.adaptiveThreshold(
+ gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 11, 2
+ )
+
+ # Dilation to enhance thin text regions
+ kernel = np.ones((1, 1), np.uint8)
+ processed = cv2.dilate(thresh, kernel, iterations=1)
+
+ return processed
+
+def process_image(image_path):
+ """
+ Runs OCR on a preprocessed image and returns results.
+ """
+ preprocessed_img = preprocess_image(image_path)
+ result = ocr.ocr(preprocessed_img, cls=True)
+ return result
+
+# Process images and save results to a file
+with open(output_file_path, 'w', encoding='utf-8') as output_file:
+ output_file.write("Filename,Recognized Text,Confidence Score\n") # Header
+
+ for img_file in os.listdir(img_folder_path):
+ if img_file.lower().endswith((".png", ".jpg", ".jpeg")):
+ img_path = os.path.join(img_folder_path, img_file)
+ try:
+ ocr_results = process_image(img_path)
+ for res in ocr_results:
+ for bbox, (text, score) in res:
+ output_file.write(f"{img_file},{text},{score:.4f}\n")
+ except Exception as e:
+ print(f"Error processing {img_file}: {e}")
+
+print("✅ OCR processing complete. Results saved to:", output_file_path)
diff --git a/models/cvModel/local_paddleocr_model/rec/inference.pdiparams b/models/cvModel/local_paddleocr_model/rec/inference.pdiparams
new file mode 100644
index 0000000..49dac84
Binary files /dev/null and b/models/cvModel/local_paddleocr_model/rec/inference.pdiparams differ
diff --git a/models/cvModel/local_paddleocr_model/rec/inference.pdiparams.info b/models/cvModel/local_paddleocr_model/rec/inference.pdiparams.info
new file mode 100644
index 0000000..b1c33cb
Binary files /dev/null and b/models/cvModel/local_paddleocr_model/rec/inference.pdiparams.info differ
diff --git a/models/cvModel/local_paddleocr_model/rec/inference.pdmodel b/models/cvModel/local_paddleocr_model/rec/inference.pdmodel
new file mode 100644
index 0000000..d0a593c
Binary files /dev/null and b/models/cvModel/local_paddleocr_model/rec/inference.pdmodel differ
diff --git a/models/cvModel/model.py b/models/cvModel/model.py
new file mode 100644
index 0000000..c084ec7
--- /dev/null
+++ b/models/cvModel/model.py
@@ -0,0 +1,44 @@
+from paddleocr import PaddleOCR
+
+class OCRProcessor:
+ def __init__(self, image_path='input_image.jpg', output_path='./input.txt', language='en'):
+ """
+ Initialize the OCRProcessor.
+
+ Args:
+ - image_path (str): Path to the input image.
+ Default is "input_image.jpg"
+ - output_path (str): Path to the output file where OCR results will be saved.
+ Default is './input.txt' since it is used as input for the LLM Model.
+ - language (str): Language to use for OCR. Default is 'en' (English).
+ """
+ self.image_path = image_path
+ self.output_path = output_path
+ self.language = language
+
+ # Initialize PaddleOCR with specified language
+ self.ocr = PaddleOCR(use_angle_cls=True, lang=language)
+
+ def process_image(self):
+ """
+ Process the input image and perform OCR.
+ """
+ # Perform OCR on the image
+ result = self.ocr.ocr(self.image_path, cls=True)
+
+ # Print the OCR results
+ for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+
+ # Write OCR results to a file
+ with open(self.output_path, 'w') as file:
+ for entry in result:
+ for bbox, (text, score) in entry:
+ print(text)
+ # Add extra spaces before text if x-coordinate of the bounding box is greater than 5
+ if bbox[0][0] > 5:
+ file.write(" ")
+
+ file.write(f"{text}\n")
diff --git a/models/cvModel/new_model.h5/inference.pdiparams b/models/cvModel/new_model.h5/inference.pdiparams
new file mode 100644
index 0000000..26ba0c9
Binary files /dev/null and b/models/cvModel/new_model.h5/inference.pdiparams differ
diff --git a/models/cvModel/new_model.h5/inference.pdiparams.info b/models/cvModel/new_model.h5/inference.pdiparams.info
new file mode 100644
index 0000000..1cdccfc
Binary files /dev/null and b/models/cvModel/new_model.h5/inference.pdiparams.info differ
diff --git a/models/cvModel/new_model.h5/inference.pdmodel b/models/cvModel/new_model.h5/inference.pdmodel
new file mode 100644
index 0000000..5dfe4cf
Binary files /dev/null and b/models/cvModel/new_model.h5/inference.pdmodel differ
diff --git a/cvModel/output_acc.txt b/models/cvModel/output_acc.txt
similarity index 90%
rename from cvModel/output_acc.txt
rename to models/cvModel/output_acc.txt
index a82518d..2ae60fb 100644
--- a/cvModel/output_acc.txt
+++ b/models/cvModel/output_acc.txt
@@ -1,2 +1,2 @@
- NOM
- BALTHAZAR
+ NOM
+ BALTHAZAR
diff --git a/models/cvModel/output_paddle.txt b/models/cvModel/output_paddle.txt
new file mode 100644
index 0000000..85998a7
--- /dev/null
+++ b/models/cvModel/output_paddle.txt
@@ -0,0 +1,6 @@
+ABgDEFGhTTKLmNOPGRo+UVWXYZ
+abcdefahii k|mnopqr6+uvwxyz
+And this bird uou cannot chanae
+Lord knows I Eant chanae
+Lord help me I cantchanae
+Won't you fly high Free bird ueah
\ No newline at end of file
diff --git a/cvModel/test.py b/models/cvModel/test.py
similarity index 97%
rename from cvModel/test.py
rename to models/cvModel/test.py
index 13b7223..70e5a72 100644
--- a/cvModel/test.py
+++ b/models/cvModel/test.py
@@ -1,27 +1,27 @@
-from paddleocr import PaddleOCR,draw_ocr
-
-# Paddleocr supports Chinese, English, French, German, Korean and Japanese.
-# You can set the parameter `lang` as `ch`, `en`, `fr`, `german`, `korean`, `japan`
-# to switch the language model in order.
-local_model_dir = './local_paddleocr_model/'
-
-# Initialize PaddleOCR with the local model directory
-ocr = PaddleOCR(use_angle_cls=True, lang='en', rec_model_dir=f'{local_model_dir}/rec')
-img_path = './traindata/TRAIN_00001.jpg'
-result = ocr.ocr(img_path, cls=True)
-
-
-for idx in range(len(result)):
- res = result[idx]
- for line in res:
- print(line)
-
-# Iterate over each result to print or write to a file
-with open('./output.txt', 'w') as file:
- for entry in result: # Each entry is a list of results for a line
- for bbox, (text, score) in entry: # Unpack the bounding box and text details
- print(text) # Print text to the console
- if bbox[0][0] > 5:
- file.write(f" ") # Write text to file# Check the x-coordinate of the
-
+from paddleocr import PaddleOCR,draw_ocr
+
+# Paddleocr supports Chinese, English, French, German, Korean and Japanese.
+# You can set the parameter `lang` as `ch`, `en`, `fr`, `german`, `korean`, `japan`
+# to switch the language model in order.
+local_model_dir = './local_paddleocr_model/'
+
+# Initialize PaddleOCR with the local model directory
+ocr = PaddleOCR(use_angle_cls=True, lang='en', rec_model_dir=f'{local_model_dir}/rec')
+img_path = './traindata/TRAIN_00001.jpg'
+result = ocr.ocr(img_path, cls=True)
+
+
+for idx in range(len(result)):
+ res = result[idx]
+ for line in res:
+ print(line)
+
+# Iterate over each result to print or write to a file
+with open('./output.txt', 'w') as file:
+ for entry in result: # Each entry is a list of results for a line
+ for bbox, (text, score) in entry: # Unpack the bounding box and text details
+ print(text) # Print text to the console
+ if bbox[0][0] > 5:
+ file.write(f" ") # Write text to file# Check the x-coordinate of the
+
file.write(f"{text}\n") # Write text to file
\ No newline at end of file
diff --git a/models/cvModel/test1.jpg b/models/cvModel/test1.jpg
new file mode 100644
index 0000000..5d4b45a
Binary files /dev/null and b/models/cvModel/test1.jpg differ
diff --git a/models/cvModel/test2.jpg b/models/cvModel/test2.jpg
new file mode 100644
index 0000000..673ced4
Binary files /dev/null and b/models/cvModel/test2.jpg differ
diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json
new file mode 100644
index 0000000..71d0e11
--- /dev/null
+++ b/node_modules/.package-lock.json
@@ -0,0 +1,7 @@
+{
+ "name": "inkwave",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {}
+}
diff --git a/output.txt b/output.txt
new file mode 100644
index 0000000..58cb015
--- /dev/null
+++ b/output.txt
@@ -0,0 +1 @@
+Filename,Recognized Text,Confidence Score
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..fc0b036
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,13 @@
+{
+ "name": "inkwave",
+ "version": "1.0.0",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "inkwave",
+ "version": "1.0.0",
+ "license": "ISC"
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..0f8cc45
--- /dev/null
+++ b/package.json
@@ -0,0 +1,12 @@
+{
+ "name": "inkwave",
+ "version": "1.0.0",
+ "description": "InkWave is all about developing a machine learning model capable of converting handwritten notes into digitized ones. The digitized text would be used to be formatted into various files, including pdf and markdown. Ultimately we would be building a UI for the ML model so that we can see it in works and use it when required easily. We would be using Computer Vision, Natural Language Processing and Large Language Model in order to develop it.",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC"
+}
diff --git a/server/.gitignore b/server/.gitignore
new file mode 100644
index 0000000..5121d93
--- /dev/null
+++ b/server/.gitignore
@@ -0,0 +1,11 @@
+# dependencies
+/node_modules
+/server/node_modules
+
+# misc
+.DS_Store
+.env
+.env.local
+.env.development.local
+.env.test.local
+.env.production.local
\ No newline at end of file
diff --git a/server/README.md b/server/README.md
new file mode 100644
index 0000000..8993082
--- /dev/null
+++ b/server/README.md
@@ -0,0 +1,51 @@
+# How to Run Demo
+
+### Change directory to Frontend folder (this command is from the root of the project)
+
+Command: `cd Frontend`
+
+### Install dependencies in the Frontend folder if the dependencies have not been installed
+
+Command: `npm install`
+
+### Start the demo of the application
+
+Command: `npm run dev`
+
+# BACKEND
+
+Backend is responsible for routing data between frontend, ml models, and the database.
+
+### API
+
+TBD
+
+### SCHEMAS
+
+##### UserSchema:
+
+uuid : string
+creationDate : date
+deletionDate : date / null
+lastUpdated : date
+email : string (add email pattern)
+password : string (hashed)
+
+##### NoteSchema:
+
+objectID : objectId
+userID : UserSchema
+name : string
+image : ImageSchema
+md : SummarySchema
+lastUpdated : date
+
+##### ImageSchema:
+
+objectID : objectId
+image : binData ([GridFS for Self-Managed Deployments - MongoDB Manual](https://www.mongodb.com/docs/manual/core/gridfs/) or single document if under 16 MB)
+
+##### SummarySchema:
+
+objectID : objectId
+md : binData ([GridFS for Self-Managed Deployments - MongoDB Manual](https://www.mongodb.com/docs/manual/core/gridfs/) or single document if under 16 MB)
diff --git a/server/package-lock.json b/server/package-lock.json
new file mode 100644
index 0000000..27eb5c2
--- /dev/null
+++ b/server/package-lock.json
@@ -0,0 +1,2025 @@
+{
+ "name": "inkwave",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "inkwave",
+ "version": "1.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "bcryptjs": "^2.4.3",
+ "cors": "^2.8.5",
+ "dotenv": "^16.4.5",
+ "express": "^4.21.2",
+ "mongodb": "^6.10.0",
+ "mongoose": "^8.8.1",
+ "nodemailer": "^6.9.16",
+ "validator": "^13.12.0"
+ },
+ "devDependencies": {
+ "@types/express": "^5.0.0",
+ "@types/node": "^22.10.1",
+ "electron": "^33.2.0",
+ "ts-node": "^10.9.2",
+ "typescript": "^5.7.2"
+ }
+ },
+ "node_modules/@cspotcode/source-map-support": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
+ "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "0.3.9"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@electron/get": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@electron/get/-/get-2.0.3.tgz",
+ "integrity": "sha512-Qkzpg2s9GnVV2I2BjRksUi43U5e6+zaQMcjoJy0C+C5oxaKl+fmckGDQFtRpZpZV0NQekuZZ+tGz7EA9TVnQtQ==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.1.1",
+ "env-paths": "^2.2.0",
+ "fs-extra": "^8.1.0",
+ "got": "^11.8.5",
+ "progress": "^2.0.3",
+ "semver": "^6.2.0",
+ "sumchecker": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "global-agent": "^3.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+ "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
+ "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.0.3",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
+ "node_modules/@mongodb-js/saslprep": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.9.tgz",
+ "integrity": "sha512-tVkljjeEaAhCqTzajSdgbQ6gE6f3oneVwa3iXR6csiEwXXOFsiC6Uh9iAjAhXPtqa/XMDHWjjeNH/77m/Yq2dw==",
+ "dependencies": {
+ "sparse-bitfield": "^3.0.3"
+ }
+ },
+ "node_modules/@sindresorhus/is": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz",
+ "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/is?sponsor=1"
+ }
+ },
+ "node_modules/@szmarczak/http-timer": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz",
+ "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==",
+ "dev": true,
+ "dependencies": {
+ "defer-to-connect": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@tsconfig/node10": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz",
+ "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node12": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
+ "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node14": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
+ "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@tsconfig/node16": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
+ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/body-parser": {
+ "version": "1.19.5",
+ "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
+ "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==",
+ "dev": true,
+ "dependencies": {
+ "@types/connect": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/cacheable-request": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz",
+ "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==",
+ "dev": true,
+ "dependencies": {
+ "@types/http-cache-semantics": "*",
+ "@types/keyv": "^3.1.4",
+ "@types/node": "*",
+ "@types/responselike": "^1.0.0"
+ }
+ },
+ "node_modules/@types/connect": {
+ "version": "3.4.38",
+ "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz",
+ "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/express": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.0.tgz",
+ "integrity": "sha512-DvZriSMehGHL1ZNLzi6MidnsDhUZM/x2pRdDIKdwbUNqqwHxMlRdkxtn6/EPKyqKpHqTl/4nRZsRNLpZxZRpPQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/body-parser": "*",
+ "@types/express-serve-static-core": "^5.0.0",
+ "@types/qs": "*",
+ "@types/serve-static": "*"
+ }
+ },
+ "node_modules/@types/express-serve-static-core": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.2.tgz",
+ "integrity": "sha512-vluaspfvWEtE4vcSDlKRNer52DvOGrB2xv6diXy6UKyKW0lqZiWHGNApSyxOv+8DE5Z27IzVvE7hNkxg7EXIcg==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*",
+ "@types/qs": "*",
+ "@types/range-parser": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/http-cache-semantics": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz",
+ "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==",
+ "dev": true
+ },
+ "node_modules/@types/http-errors": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz",
+ "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==",
+ "dev": true
+ },
+ "node_modules/@types/keyv": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz",
+ "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/mime": {
+ "version": "1.3.5",
+ "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz",
+ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "22.10.1",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz",
+ "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==",
+ "dev": true,
+ "dependencies": {
+ "undici-types": "~6.20.0"
+ }
+ },
+ "node_modules/@types/qs": {
+ "version": "6.9.17",
+ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.17.tgz",
+ "integrity": "sha512-rX4/bPcfmvxHDv0XjfJELTTr+iB+tn032nPILqHm5wbthUUUuVtNGGqzhya9XUxjTP8Fpr0qYgSZZKxGY++svQ==",
+ "dev": true
+ },
+ "node_modules/@types/range-parser": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz",
+ "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==",
+ "dev": true
+ },
+ "node_modules/@types/responselike": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz",
+ "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==",
+ "dev": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/send": {
+ "version": "0.17.4",
+ "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz",
+ "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==",
+ "dev": true,
+ "dependencies": {
+ "@types/mime": "^1",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/serve-static": {
+ "version": "1.15.7",
+ "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz",
+ "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==",
+ "dev": true,
+ "dependencies": {
+ "@types/http-errors": "*",
+ "@types/node": "*",
+ "@types/send": "*"
+ }
+ },
+ "node_modules/@types/webidl-conversions": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
+ "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA=="
+ },
+ "node_modules/@types/whatwg-url": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
+ "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
+ "dependencies": {
+ "@types/webidl-conversions": "*"
+ }
+ },
+ "node_modules/@types/yauzl": {
+ "version": "2.10.3",
+ "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz",
+ "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/accepts": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
+ "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
+ "dependencies": {
+ "mime-types": "~2.1.34",
+ "negotiator": "0.6.3"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.14.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
+ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-walk": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz",
+ "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "acorn": "^8.11.0"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/arg": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
+ "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/array-flatten": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
+ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg=="
+ },
+ "node_modules/bcryptjs": {
+ "version": "2.4.3",
+ "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
+ "integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ=="
+ },
+ "node_modules/body-parser": {
+ "version": "1.20.3",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
+ "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "content-type": "~1.0.5",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "on-finished": "2.4.1",
+ "qs": "6.13.0",
+ "raw-body": "2.5.2",
+ "type-is": "~1.6.18",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/body-parser/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/body-parser/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/boolean": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/boolean/-/boolean-3.2.0.tgz",
+ "integrity": "sha512-d0II/GO9uf9lfUHH2BQsjxzRJZBdsjgsBiW4BvhWk/3qoKwQFjIDVN19PfX8F2D/r9PCMTtLWjYVCFrpeYUzsw==",
+ "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/bson": {
+ "version": "6.9.0",
+ "resolved": "https://registry.npmjs.org/bson/-/bson-6.9.0.tgz",
+ "integrity": "sha512-X9hJeyeM0//Fus+0pc5dSUMhhrrmWwQUtdavaQeF3Ta6m69matZkGWV/MrBcnwUeLC8W9kwwc2hfkZgUuCX3Ig==",
+ "engines": {
+ "node": ">=16.20.1"
+ }
+ },
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/bytes": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
+ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/cacheable-lookup": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
+ "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10.6.0"
+ }
+ },
+ "node_modules/cacheable-request": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz",
+ "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==",
+ "dev": true,
+ "dependencies": {
+ "clone-response": "^1.0.2",
+ "get-stream": "^5.1.0",
+ "http-cache-semantics": "^4.0.0",
+ "keyv": "^4.0.0",
+ "lowercase-keys": "^2.0.0",
+ "normalize-url": "^6.0.1",
+ "responselike": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz",
+ "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/clone-response": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz",
+ "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==",
+ "dev": true,
+ "dependencies": {
+ "mimic-response": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/content-type": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz",
+ "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz",
+ "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/cookie-signature": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
+ "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
+ },
+ "node_modules/cors": {
+ "version": "2.8.5",
+ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
+ "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
+ "dependencies": {
+ "object-assign": "^4",
+ "vary": "^1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/create-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
+ "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.3.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+ "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decompress-response": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
+ "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
+ "dev": true,
+ "dependencies": {
+ "mimic-response": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/decompress-response/node_modules/mimic-response": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
+ "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/defer-to-connect": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz",
+ "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
+ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/depd": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
+ "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/destroy": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
+ "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
+ "engines": {
+ "node": ">= 0.8",
+ "npm": "1.2.8000 || >= 1.4.16"
+ }
+ },
+ "node_modules/detect-node": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz",
+ "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/diff": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
+ "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.4.5",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
+ "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/ee-first": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
+ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
+ },
+ "node_modules/electron": {
+ "version": "33.2.0",
+ "resolved": "https://registry.npmjs.org/electron/-/electron-33.2.0.tgz",
+ "integrity": "sha512-PVw1ICAQDPsnnsmpNFX/b1i/49h67pbSPxuIENd9K9WpGO1tsRaQt+K2bmXqTuoMJsbzIc75Ce8zqtuwBPqawA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "@electron/get": "^2.0.0",
+ "@types/node": "^20.9.0",
+ "extract-zip": "^2.0.1"
+ },
+ "bin": {
+ "electron": "cli.js"
+ },
+ "engines": {
+ "node": ">= 12.20.55"
+ }
+ },
+ "node_modules/electron/node_modules/@types/node": {
+ "version": "20.17.9",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.9.tgz",
+ "integrity": "sha512-0JOXkRyLanfGPE2QRCwgxhzlBAvaRdCNMcvbd7jFfpmD4eEXll7LRwy5ymJmyeZqk7Nh7eD2LeUyQ68BbndmXw==",
+ "dev": true,
+ "dependencies": {
+ "undici-types": "~6.19.2"
+ }
+ },
+ "node_modules/electron/node_modules/undici-types": {
+ "version": "6.19.8",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
+ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
+ "dev": true
+ },
+ "node_modules/encodeurl": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
+ "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz",
+ "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==",
+ "dependencies": {
+ "get-intrinsic": "^1.2.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es6-error": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
+ "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/escape-html": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
+ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow=="
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/etag": {
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
+ "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/express": {
+ "version": "4.21.2",
+ "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
+ "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
+ "dependencies": {
+ "accepts": "~1.3.8",
+ "array-flatten": "1.1.1",
+ "body-parser": "1.20.3",
+ "content-disposition": "0.5.4",
+ "content-type": "~1.0.4",
+ "cookie": "0.7.1",
+ "cookie-signature": "1.0.6",
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "finalhandler": "1.3.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "merge-descriptors": "1.0.3",
+ "methods": "~1.1.2",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "0.1.12",
+ "proxy-addr": "~2.0.7",
+ "qs": "6.13.0",
+ "range-parser": "~1.2.1",
+ "safe-buffer": "5.2.1",
+ "send": "0.19.0",
+ "serve-static": "1.16.2",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "type-is": "~1.6.18",
+ "utils-merge": "1.0.1",
+ "vary": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/express/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/express/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/extract-zip": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
+ "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.1.1",
+ "get-stream": "^5.1.0",
+ "yauzl": "^2.10.0"
+ },
+ "bin": {
+ "extract-zip": "cli.js"
+ },
+ "engines": {
+ "node": ">= 10.17.0"
+ },
+ "optionalDependencies": {
+ "@types/yauzl": "^2.9.1"
+ }
+ },
+ "node_modules/fd-slicer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+ "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==",
+ "dev": true,
+ "dependencies": {
+ "pend": "~1.2.0"
+ }
+ },
+ "node_modules/finalhandler": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz",
+ "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==",
+ "dependencies": {
+ "debug": "2.6.9",
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "on-finished": "2.4.1",
+ "parseurl": "~1.3.3",
+ "statuses": "2.0.1",
+ "unpipe": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/finalhandler/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/finalhandler/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/forwarded": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz",
+ "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fresh": {
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
+ "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+ "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=6 <7 || >=8"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz",
+ "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "has-proto": "^1.0.1",
+ "has-symbols": "^1.0.3",
+ "hasown": "^2.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-stream": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "dev": true,
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/global-agent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/global-agent/-/global-agent-3.0.0.tgz",
+ "integrity": "sha512-PT6XReJ+D07JvGoxQMkT6qji/jVNfX/h364XHZOWeRzy64sSFr+xJ5OX7LI3b4MPQzdL4H8Y8M0xzPpsVMwA8Q==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "boolean": "^3.0.1",
+ "es6-error": "^4.1.1",
+ "matcher": "^3.0.0",
+ "roarr": "^2.15.3",
+ "semver": "^7.3.2",
+ "serialize-error": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=10.0"
+ }
+ },
+ "node_modules/global-agent/node_modules/semver": {
+ "version": "7.6.3",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
+ "dev": true,
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
+ "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
+ "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
+ "dependencies": {
+ "get-intrinsic": "^1.1.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/got": {
+ "version": "11.8.6",
+ "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz",
+ "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==",
+ "dev": true,
+ "dependencies": {
+ "@sindresorhus/is": "^4.0.0",
+ "@szmarczak/http-timer": "^4.0.5",
+ "@types/cacheable-request": "^6.0.1",
+ "@types/responselike": "^1.0.0",
+ "cacheable-lookup": "^5.0.3",
+ "cacheable-request": "^7.0.2",
+ "decompress-response": "^6.0.0",
+ "http2-wrapper": "^1.0.0-beta.5.2",
+ "lowercase-keys": "^2.0.0",
+ "p-cancelable": "^2.0.0",
+ "responselike": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.19.0"
+ },
+ "funding": {
+ "url": "https://github.com/sindresorhus/got?sponsor=1"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz",
+ "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/http-cache-semantics": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
+ "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
+ "dev": true
+ },
+ "node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/http2-wrapper": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz",
+ "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==",
+ "dev": true,
+ "dependencies": {
+ "quick-lru": "^5.1.1",
+ "resolve-alpn": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=10.19.0"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.4.24",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/ipaddr.js": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
+ "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true
+ },
+ "node_modules/json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/jsonfile": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+ "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
+ "dev": true,
+ "optionalDependencies": {
+ "graceful-fs": "^4.1.6"
+ }
+ },
+ "node_modules/kareem": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
+ "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/lowercase-keys": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
+ "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/make-error": {
+ "version": "1.3.6",
+ "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
+ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/matcher": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/matcher/-/matcher-3.0.0.tgz",
+ "integrity": "sha512-OkeDaAZ/bQCxeFAozM55PKcKU0yJMPGifLwV4Qgjitu+5MoAfSQN4lsLJeXZ1b8w0x+/Emda6MZgXS1jvsapng==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "escape-string-regexp": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/media-typer": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
+ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/memory-pager": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
+ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg=="
+ },
+ "node_modules/merge-descriptors": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz",
+ "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==",
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/methods": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
+ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
+ "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "bin": {
+ "mime": "cli.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-response": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
+ "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/mongodb": {
+ "version": "6.10.0",
+ "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.10.0.tgz",
+ "integrity": "sha512-gP9vduuYWb9ZkDM546M+MP2qKVk5ZG2wPF63OvSRuUbqCR+11ZCAE1mOfllhlAG0wcoJY5yDL/rV3OmYEwXIzg==",
+ "dependencies": {
+ "@mongodb-js/saslprep": "^1.1.5",
+ "bson": "^6.7.0",
+ "mongodb-connection-string-url": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=16.20.1"
+ },
+ "peerDependencies": {
+ "@aws-sdk/credential-providers": "^3.188.0",
+ "@mongodb-js/zstd": "^1.1.0",
+ "gcp-metadata": "^5.2.0",
+ "kerberos": "^2.0.1",
+ "mongodb-client-encryption": ">=6.0.0 <7",
+ "snappy": "^7.2.2",
+ "socks": "^2.7.1"
+ },
+ "peerDependenciesMeta": {
+ "@aws-sdk/credential-providers": {
+ "optional": true
+ },
+ "@mongodb-js/zstd": {
+ "optional": true
+ },
+ "gcp-metadata": {
+ "optional": true
+ },
+ "kerberos": {
+ "optional": true
+ },
+ "mongodb-client-encryption": {
+ "optional": true
+ },
+ "snappy": {
+ "optional": true
+ },
+ "socks": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/mongodb-connection-string-url": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz",
+ "integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==",
+ "dependencies": {
+ "@types/whatwg-url": "^11.0.2",
+ "whatwg-url": "^13.0.0"
+ }
+ },
+ "node_modules/mongoose": {
+ "version": "8.8.1",
+ "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.8.1.tgz",
+ "integrity": "sha512-l7DgeY1szT98+EKU8GYnga5WnyatAu+kOQ2VlVX1Mxif6A0Umt0YkSiksCiyGxzx8SPhGe9a53ND1GD4yVDrPA==",
+ "dependencies": {
+ "bson": "^6.7.0",
+ "kareem": "2.6.3",
+ "mongodb": "~6.10.0",
+ "mpath": "0.9.0",
+ "mquery": "5.0.0",
+ "ms": "2.1.3",
+ "sift": "17.1.3"
+ },
+ "engines": {
+ "node": ">=16.20.1"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mongoose"
+ }
+ },
+ "node_modules/mpath": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
+ "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/mquery": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
+ "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
+ "dependencies": {
+ "debug": "4.x"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/nodemailer": {
+ "version": "6.9.16",
+ "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz",
+ "integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/normalize-url": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz",
+ "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.3",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz",
+ "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/on-finished": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
+ "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/p-cancelable": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz",
+ "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/parseurl": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
+ "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/path-to-regexp": {
+ "version": "0.1.12",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+ "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ=="
+ },
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==",
+ "dev": true
+ },
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/proxy-addr": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz",
+ "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==",
+ "dependencies": {
+ "forwarded": "0.2.0",
+ "ipaddr.js": "1.9.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/pump": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.2.tgz",
+ "integrity": "sha512-tUPXtzlGM8FE3P0ZL6DVs/3P58k9nk8/jZeQCurTJylQA8qFYzHFfhBJkuqyE0FifOsQ0uKWekiZ5g8wtr28cw==",
+ "dev": true,
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.13.0",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
+ "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
+ "dependencies": {
+ "side-channel": "^1.0.6"
+ },
+ "engines": {
+ "node": ">=0.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/quick-lru": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
+ "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/range-parser": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
+ "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/raw-body": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
+ "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "dependencies": {
+ "bytes": "3.1.2",
+ "http-errors": "2.0.0",
+ "iconv-lite": "0.4.24",
+ "unpipe": "1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/resolve-alpn": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz",
+ "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==",
+ "dev": true
+ },
+ "node_modules/responselike": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz",
+ "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==",
+ "dev": true,
+ "dependencies": {
+ "lowercase-keys": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/roarr": {
+ "version": "2.15.4",
+ "resolved": "https://registry.npmjs.org/roarr/-/roarr-2.15.4.tgz",
+ "integrity": "sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "boolean": "^3.0.1",
+ "detect-node": "^2.0.4",
+ "globalthis": "^1.0.1",
+ "json-stringify-safe": "^5.0.1",
+ "semver-compare": "^1.0.0",
+ "sprintf-js": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/semver-compare": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
+ "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/send": {
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz",
+ "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==",
+ "dependencies": {
+ "debug": "2.6.9",
+ "depd": "2.0.0",
+ "destroy": "1.2.0",
+ "encodeurl": "~1.0.2",
+ "escape-html": "~1.0.3",
+ "etag": "~1.8.1",
+ "fresh": "0.5.2",
+ "http-errors": "2.0.0",
+ "mime": "1.6.0",
+ "ms": "2.1.3",
+ "on-finished": "2.4.1",
+ "range-parser": "~1.2.1",
+ "statuses": "2.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/send/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/send/node_modules/debug/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ },
+ "node_modules/send/node_modules/encodeurl": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
+ "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/serialize-error": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-7.0.1.tgz",
+ "integrity": "sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "type-fest": "^0.13.1"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/serve-static": {
+ "version": "1.16.2",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz",
+ "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==",
+ "dependencies": {
+ "encodeurl": "~2.0.0",
+ "escape-html": "~1.0.3",
+ "parseurl": "~1.3.3",
+ "send": "0.19.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/set-function-length": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/setprototypeof": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
+ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
+ },
+ "node_modules/side-channel": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz",
+ "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.4",
+ "object-inspect": "^1.13.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/sift": {
+ "version": "17.1.3",
+ "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
+ "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ=="
+ },
+ "node_modules/sparse-bitfield": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
+ "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
+ "dependencies": {
+ "memory-pager": "^1.0.2"
+ }
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
+ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/statuses": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
+ "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/sumchecker": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/sumchecker/-/sumchecker-3.0.1.tgz",
+ "integrity": "sha512-MvjXzkz/BOfyVDkG0oFOtBxHX2u3gKbMHIF/dXblZsgD3BWOFLmHovIpZY7BykJdAjcqRCBi1WYBNdEC9yI7vg==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.1.0"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ }
+ },
+ "node_modules/toidentifier": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
+ "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
+ "integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
+ "dependencies": {
+ "punycode": "^2.3.0"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/ts-node": {
+ "version": "10.9.2",
+ "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
+ "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@cspotcode/source-map-support": "^0.8.0",
+ "@tsconfig/node10": "^1.0.7",
+ "@tsconfig/node12": "^1.0.7",
+ "@tsconfig/node14": "^1.0.0",
+ "@tsconfig/node16": "^1.0.2",
+ "acorn": "^8.4.1",
+ "acorn-walk": "^8.1.1",
+ "arg": "^4.1.0",
+ "create-require": "^1.1.0",
+ "diff": "^4.0.1",
+ "make-error": "^1.1.1",
+ "v8-compile-cache-lib": "^3.0.1",
+ "yn": "3.1.1"
+ },
+ "bin": {
+ "ts-node": "dist/bin.js",
+ "ts-node-cwd": "dist/bin-cwd.js",
+ "ts-node-esm": "dist/bin-esm.js",
+ "ts-node-script": "dist/bin-script.js",
+ "ts-node-transpile-only": "dist/bin-transpile.js",
+ "ts-script": "dist/bin-script-deprecated.js"
+ },
+ "peerDependencies": {
+ "@swc/core": ">=1.2.50",
+ "@swc/wasm": ">=1.2.50",
+ "@types/node": "*",
+ "typescript": ">=2.7"
+ },
+ "peerDependenciesMeta": {
+ "@swc/core": {
+ "optional": true
+ },
+ "@swc/wasm": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.13.1.tgz",
+ "integrity": "sha512-34R7HTnG0XIJcBSn5XhDd7nNFPRcXYRZrBB2O2jdKqYODldSzBAqzsWoZYYvduky73toYS/ESqxPvkDf/F0XMg==",
+ "dev": true,
+ "optional": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/type-is": {
+ "version": "1.6.18",
+ "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
+ "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==",
+ "dependencies": {
+ "media-typer": "0.3.0",
+ "mime-types": "~2.1.24"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
+ "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.20.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
+ "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
+ "dev": true
+ },
+ "node_modules/universalify": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+ "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/unpipe": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
+ "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/utils-merge": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
+ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/v8-compile-cache-lib": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
+ "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/validator": {
+ "version": "13.12.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.12.0.tgz",
+ "integrity": "sha512-c1Q0mCiPlgdTVVVIJIrBuxNicYE+t/7oKeI9MWLj3fh/uq2Pxh/3eeWbVZ4OcGW1TUf53At0njHw5SMdA3tmMg==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/vary": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
+ "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "13.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz",
+ "integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==",
+ "dependencies": {
+ "tr46": "^4.1.1",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true
+ },
+ "node_modules/yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==",
+ "dev": true,
+ "dependencies": {
+ "buffer-crc32": "~0.2.3",
+ "fd-slicer": "~1.1.0"
+ }
+ },
+ "node_modules/yn": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
+ "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ }
+ }
+}
diff --git a/server/package.json b/server/package.json
new file mode 100644
index 0000000..8bdc59a
--- /dev/null
+++ b/server/package.json
@@ -0,0 +1,37 @@
+{
+ "name": "inkwave",
+ "version": "1.0.0",
+ "description": "converts handwritten notes into digital text",
+ "main": "server.ts",
+ "scripts": {
+ "start": "electron .",
+ "server": "npx ts-node src/server.ts"
+ },
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/cppsea/InkWave.git"
+ },
+ "author": "",
+ "license": "MIT",
+ "bugs": {
+ "url": "https://github.com/cppsea/InkWave/issues"
+ },
+ "homepage": "https://github.com/cppsea/InkWave#readme",
+ "devDependencies": {
+ "@types/express": "^5.0.0",
+ "@types/node": "^22.10.1",
+ "electron": "^33.2.0",
+ "ts-node": "^10.9.2",
+ "typescript": "^5.7.2"
+ },
+ "dependencies": {
+ "bcryptjs": "^2.4.3",
+ "cors": "^2.8.5",
+ "dotenv": "^16.4.5",
+ "express": "^4.21.2",
+ "mongodb": "^6.10.0",
+ "mongoose": "^8.8.1",
+ "nodemailer": "^6.9.16",
+ "validator": "^13.12.0"
+ }
+}
diff --git a/server/src/app.ts b/server/src/app.ts
new file mode 100644
index 0000000..b8c2461
--- /dev/null
+++ b/server/src/app.ts
@@ -0,0 +1,21 @@
+// index.ts
+import express from "express";
+const cors = require("cors");
+
+import authRouter from "./routes/auth.ts";
+import notesRouter from "./routes/notes.ts";
+import usersRouter from "./routes/users.ts";
+import testRouter from "./routes/test.ts";
+
+const app = express();
+
+app.use(cors());
+app.use(express.json());
+
+// Routes
+app.use("/api/auth", authRouter);
+app.use("/api/notes", notesRouter);
+app.use("/api/users", usersRouter);
+app.use("/api/test", testRouter);
+
+export default app;
diff --git a/server/src/controllers/auth.ts b/server/src/controllers/auth.ts
new file mode 100644
index 0000000..e38528d
--- /dev/null
+++ b/server/src/controllers/auth.ts
@@ -0,0 +1,87 @@
+import { Request, Response } from "express";
+import User from "../models/UserSchema.ts";
+import Note, { NoteInterface } from "../models/NoteSchema.ts";
+
+const validator = require("validator");
+const bcrypt = require("bcryptjs");
+
+// const nodemailer = require("nodemailer");
+
+// const transporter = nodemailer.createTransport({
+// host: "smtp.ethereal.email",
+// port: 587,
+// secure: false, // true for port 465, false for other ports
+// auth: {
+// user: "maddison53@ethereal.email",
+// pass: "jn7jnAPss4f63QBp6D",
+// },
+// });
+
+const login = async (req: Request, res: Response) => {
+ try {
+ const email = req.body.email;
+ const password = req.body.password;
+
+ //check for valid email first
+ if (!validator.isEmail(email)) {
+ res.status(400).send({ message: "Invalid email format" });
+ }
+
+ const user = await User.findOne({ email });
+ if (!user) {
+ res.status(401).send({ message: "Email does not exist" });
+ } else {
+ const areEqual = await bcrypt.compare(password, user.password);
+ if (areEqual) {
+ //const userData = { email: user.email, message: "Login successful" };
+ res.send(user);
+ } else {
+ res.status(401).send({ message: "Invalid password" });
+ }
+ }
+ } catch (error) {
+ console.error(error);
+ res.status(500).send({ message: "Error logging in" });
+ }
+};
+
+const register = async (req: Request, res: Response) => {
+ try {
+ const email = req.body.email;
+ const password = req.body.password;
+
+ //check for valid email
+ if (!validator.isEmail(email)) {
+ res.status(400).send({ message: "Invalid email format" });
+ }
+
+ const hashedPassword = await bcrypt.hash(password, 12);
+ const user = new User({
+ email,
+ password: hashedPassword,
+ });
+
+ const savedUser = await user.save();
+ res.send({ message: "User created", user: savedUser });
+ } catch (error) {
+ console.error(error);
+ res.status(500).send({ message: "Error creating user" });
+ }
+};
+
+const forgotPassword = async (req: Request, res: Response) => {
+ const email = req.body.email;
+
+ User.findOne({ email: email }).then((user) => {
+ if (!user) {
+ res.send("email does not exist");
+ }
+
+ //send a reset password email to user, using nodemailer library
+ });
+
+ res.send("email reset form has been sent");
+ // ** TO DO ** //
+};
+
+export { login, register, forgotPassword };
diff --git a/server/src/controllers/notes.ts b/server/src/controllers/notes.ts
new file mode 100644
index 0000000..7059e89
--- /dev/null
+++ b/server/src/controllers/notes.ts
@@ -0,0 +1,272 @@
+import { Request, Response } from "express";
+import Note, { NoteInterface } from "../models/NoteSchema.ts";
+import User from "../models/UserSchema.ts";
+
+/**
+ * Retrieves all notes for a user
+ * GET /api/notes/:userID
+ */
+const getAllNotes = async (req: Request, res: Response) => {
+ const { userID } = req.params;
+ try {
+ // Ensure userID is defined
+ if (!userID) {
+ res.status(400).send({
+ status: "error",
+ message: "Missing userID",
+ });
+ return;
+ }
+
+ // Check if user exists
+ const user = await User.findById(userID);
+ if (!user) {
+ res.status(400).send({
+ status: "error",
+ message: "User not found",
+ });
+ return;
+ }
+
+ const notes = await Note.find({ userID }).sort({ lastUpdated: -1 });
+
+ // Check if there's any notes
+ if (notes.length === 0) {
+ res.status(404).send({
+ status: "error",
+ message: "No notes found",
+ });
+ return;
+ }
+
+ // Good to go
+ res.status(200).send({
+ status: "success",
+ data: notes,
+ });
+ } catch (err) {
+ res.status(500).send({
+ status: "error",
+ message: err,
+ });
+ return;
+ }
+};
+
+/**
+ * Retrieves the most recent note for a user
+ * GET /api/notes/recent/:userID
+ */
+const getRecentNote = async (req: Request, res: Response) => {
+ const { userID } = req.params;
+ try {
+ // Ensure userID is defined
+ if (!userID) {
+ res.status(400).send({
+ status: "error",
+ message: "Missing userID",
+ });
+ return;
+ }
+
+ // Check if user exists
+ const user = await User.findById(userID);
+ if (!user) {
+ res.status(400).send({
+ status: "error",
+ message: "User not found",
+ });
+ return;
+ }
+
+ const note = await Note.findOne({ userID }).sort({ lastUpdated: -1 });
+
+ // Check if note exists
+ if (!note) {
+ res.status(404).send({
+ status: "error",
+ message: "No note found",
+ });
+ return;
+ }
+
+ // Good to go
+ res.status(200).send({
+ status: "success",
+ data: note,
+ });
+ } catch (err) {
+ res.status(500).send({
+ status: "error",
+ message: err,
+ });
+ return;
+ }
+};
+
+/**
+ * Retrieves a specific note for a user
+ * GET /api/notes/:userID/:noteID
+ */
+const getNote = async (req: Request, res: Response) => {
+ const { userID, noteID } = req.params;
+ try {
+ // Ensure userID and noteID are defined
+ if (!userID || !noteID) {
+ res.status(400).send({
+ status: "error",
+ message: "Missing userID or noteID",
+ });
+ return;
+ }
+
+ // Check if user exists
+ const user = await User.findById(userID);
+ if (!user) {
+ res.status(400).send({
+ status: "error",
+ message: "User not found",
+ });
+ return;
+ }
+
+ const note = await Note.find({ userID, _id: noteID });
+
+ // Check if note exists
+ if (note.length === 0) {
+ res.status(404).send({
+ status: "error",
+ message: "No note found",
+ });
+ return;
+ }
+
+ // Good to go
+ res.status(200).send({
+ status: "success",
+ data: note,
+ });
+ } catch (err) {
+ res.status(500).send({
+ status: "error",
+ message: err,
+ });
+ return;
+ }
+};
+
+/**
+ * Receive an image and sends it to the ML models
+ * Assuming no errors occur, this will automatically save the document into the database.
+ * POST /api/notes/summary
+ */
+const getSummary = async (req: Request, res: Response) => {
+ res.send("POST request /api/notes/summary");
+ // ** TO DO ** //
+};
+
+/**
+ * Handles saving a note to the database (editing purposes)
+ * PATCH /api/notes/save/:noteID
+ *
+ * @body {string} name - Name of the note
+ * @body {ObjectId} image - Original image/photo
+ * @body {ObjectId} md - Markdown file of the summary
+ */
+const saveNote = async (req: Request, res: Response) => {
+ const { noteID } = req.params;
+
+ try {
+ // Ensure noteID is defined
+ if (!noteID) {
+ res.status(400).send({
+ status: "error",
+ message: "Missing noteID",
+ });
+ return;
+ }
+
+ // Find the note and update the necessary fields
+ const updatedNote = await Note.findByIdAndUpdate(
+ noteID,
+ { ...req.body, lastUpdated: new Date() },
+ {
+ new: true,
+ }
+ );
+
+ // If no note was found, return an error
+ if (!updatedNote) {
+ res.status(404).send({
+ status: "error",
+ message: "Note not found",
+ });
+ return;
+ }
+
+ // Good to go
+ res.status(200).send({
+ status: "success",
+ data: updatedNote,
+ });
+ } catch (err) {
+ res.status(500).send({
+ status: "error",
+ message: err,
+ });
+ return;
+ }
+};
+
+/**
+ * Handles deleting a note from the database
+ * DELETE /api/notes/delete/:noteID
+ */
+const deleteNote = async (req: Request, res: Response) => {
+ const { noteID } = req.params;
+
+ try {
+ // Ensure noteID is defined
+ if (!noteID) {
+ res.status(400).send({
+ status: "error",
+ message: "Missing noteID",
+ });
+ return;
+ }
+
+ // Find the note and delete it
+ const deletedNote = await Note.findByIdAndDelete(noteID);
+
+ // If the note isn't found
+ if (!deletedNote) {
+ res.status(404).send({
+ status: "error",
+ message: "Note not found",
+ });
+ return;
+ }
+
+ // Good to go
+ res.status(200).send({
+ status: "success",
+ message: "Note successfully deleted",
+ data: deletedNote,
+ });
+ } catch (err) {
+ res.status(500).send({
+ status: "error",
+ message: err,
+ });
+ return;
+ }
+};
+
+export {
+ getAllNotes,
+ getNote,
+ getRecentNote,
+ getSummary,
+ saveNote,
+ deleteNote,
+};
diff --git a/server/src/controllers/test.ts b/server/src/controllers/test.ts
new file mode 100644
index 0000000..d516155
--- /dev/null
+++ b/server/src/controllers/test.ts
@@ -0,0 +1,115 @@
+import { Request, Response } from "express";
+import Note from "../models/NoteSchema.ts";
+import User from "../models/UserSchema.ts";
+
+/**
+ * Retrieves all notes
+ * GET /api/test/notes
+ */
+const getNotes = async (req: Request, res: Response) => {
+ try {
+ const notes = await Note.find().sort({ lastUpdated: -1 });
+
+ // Check if there's any notes
+ if (notes.length === 0) {
+ res.status(404).send({
+ status: "error",
+ message: "No notes found",
+ });
+ return;
+ }
+
+ // Good to go
+ res.status(200).send({
+ status: "success",
+ data: notes,
+ });
+ } catch (err) {
+ res.status(500).send({
+ status: "error",
+ message: err,
+ });
+ return;
+ }
+};
+
+/**
+ * Retrieves all users
+ * GET /api/test/users
+ */
+const getUsers = async (req: Request, res: Response) => {
+ try {
+ const users = await User.find();
+
+ // Check if there's any users
+ if (users.length === 0) {
+ res.status(404).send({
+ status: "error",
+ message: "No users found",
+ });
+ return;
+ }
+
+ // Good to go
+ res.status(200).send({
+ status: "success",
+ data: users,
+ });
+ } catch (err) {
+ res.status(500).send({
+ status: "error",
+ message: err,
+ });
+ return;
+ }
+};
+
+/**
+ * Creates a note for a user
+ * POST /api/test/create
+ */
+const createNote = async (req: Request, res: Response) => {
+ try {
+ const { userID, name, image, md } = req.body;
+
+ // Validate required fields
+ if (!userID || !name || !md) {
+ res.status(400).send({
+ status: "error",
+ message: "Missing userID, note name, or markdown content",
+ });
+ return;
+ }
+
+ // Check if user exists
+ const user = await User.findById(userID);
+ if (!user) {
+ res.status(404).send({
+ status: "error",
+ message: "User not found",
+ });
+ return;
+ }
+
+ // Create note
+ const note = await Note.create({
+ userID,
+ name,
+ image: image || null,
+ md,
+ lastUpdated: new Date(),
+ });
+
+ res.status(200).send({
+ status: "success",
+ data: note,
+ });
+ } catch (err: any) {
+ res.status(500).send({
+ status: "error",
+ message: err.message || "Internal server error",
+ });
+ }
+};
+
+export { getNotes, getUsers, createNote };
diff --git a/server/src/controllers/users.ts b/server/src/controllers/users.ts
new file mode 100644
index 0000000..296293d
--- /dev/null
+++ b/server/src/controllers/users.ts
@@ -0,0 +1,15 @@
+import { Request, Response } from "express";
+import User, { UserInterface } from "../models/UserSchema.ts";
+
+const postSignup = async (req: Request, res: Response) => {
+ const email = req.body.email;
+ const password = req.body.password;
+
+ //handle incorrect email format
+
+ // ** TO DO ** //
+};
+
+const postLogin = async (req: Request, res: Response) => {
+ // ** TO DO ** //
+};
diff --git a/server/src/models/ImageSchema.ts b/server/src/models/ImageSchema.ts
new file mode 100644
index 0000000..a7b5e04
--- /dev/null
+++ b/server/src/models/ImageSchema.ts
@@ -0,0 +1,14 @@
+import mongoose, { Schema, Document } from "mongoose";
+
+export interface ImageInterface extends Document {
+ _id : mongoose.Types.ObjectId,
+ image : mongoose.Types.Buffer,
+}
+
+const ImageSchema : Schema = new Schema({
+ _id: Schema.Types.ObjectId,
+ image: Schema.Types.Buffer,
+});
+
+const Image = mongoose.model("Image", ImageSchema);
+export default Image;
\ No newline at end of file
diff --git a/server/src/models/NoteSchema.ts b/server/src/models/NoteSchema.ts
new file mode 100644
index 0000000..dbcae80
--- /dev/null
+++ b/server/src/models/NoteSchema.ts
@@ -0,0 +1,40 @@
+import mongoose, { Schema, Document } from "mongoose";
+import { UserInterface } from "./UserSchema";
+import { ImageInterface } from "./ImageSchema";
+import { SummaryInterface } from "./SummarySchema";
+
+export interface NoteInterface extends Document {
+ userID: mongoose.Types.ObjectId | UserInterface;
+ name: string;
+ image: mongoose.Types.ObjectId | ImageInterface;
+ md: string;
+ lastUpdated: Date;
+}
+
+const NoteSchema: Schema = new Schema({
+ userID: {
+ type: Schema.Types.ObjectId,
+ ref: "User",
+ required: true,
+ },
+ name: {
+ type: String,
+ default: new Date(Date.now()).toString(),
+ },
+ image: {
+ type: Schema.Types.ObjectId,
+ ref: "Image",
+ },
+ md: {
+ type: String,
+ required: true,
+ },
+ lastUpdated: {
+ type: Date,
+ default: Date.now(),
+ required: true,
+ },
+});
+
+const Note = mongoose.model("Note", NoteSchema);
+export default Note;
diff --git a/server/src/models/README.md b/server/src/models/README.md
new file mode 100644
index 0000000..e4194c4
--- /dev/null
+++ b/server/src/models/README.md
@@ -0,0 +1,27 @@
+### UserSchema:
+
+uuid : string
+creationDate : date
+deletionDate : date / null
+lastUpdated : date
+email : string (add email pattern)
+password : string (hashed)
+
+### NoteSchema:
+
+objectID : objectId
+userID : UserSchema
+name : string
+image : ImageSchema
+md : SummarySchema
+lastUpdated : date
+
+### ImageSchema:
+
+objectID : objectId
+image : binData ([GridFS for Self-Managed Deployments - MongoDB Manual](https://www.mongodb.com/docs/manual/core/gridfs/) or single document if under 16 MB)
+
+### SummarySchema:
+
+objectID : objectId
+md : binData ([GridFS for Self-Managed Deployments - MongoDB Manual](https://www.mongodb.com/docs/manual/core/gridfs/) or single document if under 16 MB)
diff --git a/server/src/models/SummarySchema.ts b/server/src/models/SummarySchema.ts
new file mode 100644
index 0000000..29de025
--- /dev/null
+++ b/server/src/models/SummarySchema.ts
@@ -0,0 +1,14 @@
+import mongoose, { Schema, Document } from "mongoose";
+
+export interface SummaryInterface extends Document {
+ _id: mongoose.Types.ObjectId;
+ md: mongoose.Types.Buffer;
+}
+
+const SummarySchema: Schema = new Schema({
+ _id: Schema.Types.ObjectId,
+ md: Schema.Types.Buffer,
+});
+
+const Summary = mongoose.model("Summary", SummarySchema);
+export default Summary;
diff --git a/server/src/models/UserSchema.ts b/server/src/models/UserSchema.ts
new file mode 100644
index 0000000..c6dfca8
--- /dev/null
+++ b/server/src/models/UserSchema.ts
@@ -0,0 +1,26 @@
+import mongoose, { Schema, Document } from "mongoose";
+
+export interface UserInterface extends Document {
+ uuid: mongoose.Types.UUID;
+ creationDate: Date;
+ deletionDate: Date | null;
+ lastUpdated: Date;
+ email: string;
+ password: string;
+}
+
+const UserSchema: Schema = new Schema({
+ email: {
+ type: String,
+ unique: true,
+ required: true,
+ },
+ password: {
+ type: String,
+ unique: true,
+ required: true,
+ },
+});
+
+const User = mongoose.model("User", UserSchema);
+export default User;
diff --git a/server/src/routes/auth.ts b/server/src/routes/auth.ts
new file mode 100644
index 0000000..f6afa3a
--- /dev/null
+++ b/server/src/routes/auth.ts
@@ -0,0 +1,24 @@
+import express, { Request, Response } from "express";
+import * as authController from "../controllers/auth.ts";
+
+const router = express.Router();
+
+/**
+ * Handles user login and authentication
+ * @route POST /api/auth/login
+ */
+router.post("/login", authController.login);
+
+/**
+ * Handles user registration
+ * @route POST /api/auth/register
+ */
+router.post("/register", authController.register);
+
+/**
+ * Handles forgot password
+ * @route POST /api/auth/forgot-password
+ */
+router.post("/forgot-password", authController.forgotPassword);
+
+export default router;
diff --git a/server/src/routes/notes.ts b/server/src/routes/notes.ts
new file mode 100644
index 0000000..6d3f634
--- /dev/null
+++ b/server/src/routes/notes.ts
@@ -0,0 +1,47 @@
+import express, { Request, Response } from "express";
+import * as notesController from "../controllers/notes";
+
+const router = express.Router();
+
+/**
+ * Retrieves all notes for a user
+ * GET /api/notes/:userID
+ */
+router.get("/:userID", notesController.getAllNotes);
+
+/**
+ * Retrieves the most recent note for a user
+ * GET /api/notes/recent/:userID
+ */
+router.get("/recent/:userID", notesController.getRecentNote);
+
+/**
+ * Retrieves a specific note for a user
+ * GET /api/notes/:userID/:noteID
+ */
+router.get("/:userID/:noteID", notesController.getNote);
+
+/**
+ * Receive an image and sends it to the ML models
+ * Assuming no errors occur, this will automatically save the document into the database.
+ * POST /api/notes/summary
+ */
+router.post("/summary", notesController.getSummary);
+
+/**
+ * Handles saving a note to the database (editing purposes)
+ * PATCH /api/notes/save/:noteID
+ *
+ * @body {string} name - Name of the note
+ * @body {ObjectId} image - Original image/photo
+ * @body {ObjectId} md - Markdown file of the summary
+ */
+router.patch("/save/:noteID", notesController.saveNote);
+
+/**
+ * Handles deleting a note from the database
+ * DELETE /api/notes/delete/:noteID
+ */
+router.delete("/delete/:noteID", notesController.deleteNote);
+
+export default router;
diff --git a/server/src/routes/test.ts b/server/src/routes/test.ts
new file mode 100644
index 0000000..8dbc6e0
--- /dev/null
+++ b/server/src/routes/test.ts
@@ -0,0 +1,24 @@
+import express, { Request, Response } from "express";
+import * as testController from "../controllers/test";
+
+const router = express.Router();
+
+/**
+ * Retrieves all notes
+ * GET /api/test/notes
+ */
+router.get("/notes", testController.getNotes);
+
+/**
+ * Retrieves all users
+ * GET /api/test/users
+ */
+router.get("/users", testController.getUsers);
+
+/**
+ * Creates a note for a user
+ * POST /api/test/create
+ */
+router.post("/create", testController.createNote);
+
+export default router;
diff --git a/server/src/routes/users.ts b/server/src/routes/users.ts
new file mode 100644
index 0000000..6cdc3d3
--- /dev/null
+++ b/server/src/routes/users.ts
@@ -0,0 +1,5 @@
+import express, { Request, Response } from "express";
+
+const router = express.Router();
+
+export default router;
diff --git a/server/src/server.ts b/server/src/server.ts
new file mode 100644
index 0000000..b944768
--- /dev/null
+++ b/server/src/server.ts
@@ -0,0 +1,28 @@
+// server.ts
+import mongoose from "mongoose";
+import app from "./app";
+import dotenv from "dotenv";
+import seedData from "./utils/seedData";
+import User from "./models/UserSchema";
+import { join } from "path";
+
+dotenv.config();
+
+const MONGODB_URI: string = `mongodb+srv://${process.env.MONGO_USER}:${process.env.MONGO_PASSWORD}@inkwave.6yrch.mongodb.net/${process.env.MONGO_DEFAULT_DB}?retryWrites=true&w=majority&appName=inkwave`;
+const port: string | number = process.env.PORT || 1400;
+
+mongoose
+ .connect(MONGODB_URI)
+ .then(async () => {
+ console.log("Connected to DB.");
+
+ // Populate db with dummy data
+ // await seedData();
+
+ app.listen(port, () => {
+ console.log(`Server is running on http://localhost:${port}`);
+ });
+ })
+ .catch((err) => {
+ console.error("Connection to DB Cluster failed.", err);
+ });
diff --git a/server/src/test.py b/server/src/test.py
new file mode 100644
index 0000000..b7bcd6c
--- /dev/null
+++ b/server/src/test.py
@@ -0,0 +1,13 @@
+import sys
+
+def test():
+ return "Success!"
+
+def echo(s):
+ if s == "hello python!":
+ return "hello typescript!"
+
+if __name__ == "__main__":
+ s = str(sys.argv[1])
+ print(echo(s))
+ sys.stdout.flush()
\ No newline at end of file
diff --git a/server/src/test.ts b/server/src/test.ts
new file mode 100644
index 0000000..c7f094c
--- /dev/null
+++ b/server/src/test.ts
@@ -0,0 +1,25 @@
+import { spawn } from "child_process";
+
+function runPython(path : string, args : string) {
+ console.log("testing python call...");
+ console.log(args);
+ const pythonProcess = spawn("python", [path].concat(args));
+ let data = "";
+
+ pythonProcess.stdout.on("data", (chunk) => {
+ data += chunk.toString();
+ });
+ pythonProcess.stderr.on("data", (err) => {
+ console.error(`stderr: ${err}`);
+ });
+ pythonProcess.on("close", (code) => {
+ if (code != 0) {
+ console.log(`${code}`);
+ }
+ else {
+ console.log(data);
+ }
+ })
+}
+
+runPython("test.py", "hello python!");
\ No newline at end of file
diff --git a/server/src/utils/seedData.ts b/server/src/utils/seedData.ts
new file mode 100644
index 0000000..a1e9105
--- /dev/null
+++ b/server/src/utils/seedData.ts
@@ -0,0 +1,60 @@
+// src/utils/seed.ts
+import Note from "../models/NoteSchema";
+import User from "../models/UserSchema";
+
+const seedData = async () => {
+ try {
+ // Check if the collection already has data
+ const notesCount = await Note.countDocuments();
+ if (notesCount > 0) {
+ console.log("Notes collection already seeded.");
+ return;
+ }
+
+ // Dummy users
+ const user1 = new User({
+ email: "test1@gmail.com",
+ password: "test1",
+ });
+
+ const user2 = new User({
+ email: "test2@gmail.com",
+ password: "test2",
+ });
+
+ await user1.save();
+ await user2.save();
+
+ // Dummy notes
+ const dummyNotes = [
+ {
+ userID: user1._id,
+ name: "Note 1 (u1)",
+ image: null,
+ md: null,
+ lastUpdated: new Date(),
+ },
+ {
+ userID: user1._id,
+ name: "Note 2 (u1)",
+ image: null,
+ md: null,
+ lastUpdated: new Date(),
+ },
+ {
+ userID: user2._id,
+ name: "Note 1 (u2)",
+ image: null,
+ md: null,
+ lastUpdated: new Date(),
+ },
+ ];
+
+ await Note.insertMany(dummyNotes);
+ console.log("Data seeded");
+ } catch (error) {
+ console.error("Error seeding data:", error);
+ }
+};
+
+export default seedData;
diff --git a/server/tsconfig.json b/server/tsconfig.json
new file mode 100644
index 0000000..2e02c9a
--- /dev/null
+++ b/server/tsconfig.json
@@ -0,0 +1,15 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "module": "CommonJS",
+ "rootDir": "./src",
+ "moduleResolution": "node",
+ "allowImportingTsExtensions": true,
+ "noEmit": true,
+ "outDir": "src/app.ts",
+ "esModuleInterop": true,
+ "forceConsistentCasingInFileNames": true,
+ "strict": true,
+ "skipLibCheck": true
+ }
+}