diff --git a/app/api/documents.py b/app/api/documents.py index 706ca6e04..b4173782f 100644 --- a/app/api/documents.py +++ b/app/api/documents.py @@ -90,6 +90,7 @@ async def read_document(documentId: str, token=Depends(JWTBearer())): async def delete_document(documentId: str, token=Depends(JWTBearer())): """Delete a document""" try: + prisma.agentdocument.delete_many(where={"documentId": documentId}) prisma.document.delete(where={"id": documentId}) VectorStoreBase().get_database().delete(namespace=documentId) return {"success": True, "data": None} diff --git a/app/lib/documents.py b/app/lib/documents.py index 4b4b86012..37b416631 100644 --- a/app/lib/documents.py +++ b/app/lib/documents.py @@ -16,9 +16,11 @@ from langchain.embeddings.openai import OpenAIEmbeddings from llama_index.readers.schema.base import Document + from app.lib.parsers import CustomPDFPlumberLoader from app.lib.splitters import TextSplitters from app.lib.vectorstores.base import VectorStoreBase +from app.lib.loaders.sitemap import SitemapLoader valid_ingestion_types = [ "TXT", @@ -29,9 +31,18 @@ "FIRESTORE", "PSYCHIC", "GITHUB_REPOSITORY", + "WEBPAGE", + "STRIPE", + "AIRTABLE", + "SITEMAP", ] +def chunkify(lst, size): + """Divide a list into chunks of given size.""" + return [lst[i : i + size] for i in range(0, len(lst), size)] + + def upsert_document( type: str, document_id: str, @@ -52,6 +63,66 @@ def upsert_document( embeddings = OpenAIEmbeddings() + if type == "STRIPE": + pass + + if type == "AIRTABLE": + from langchain.document_loaders import AirtableLoader + + api_key = metadata["api_key"] + base_id = metadata["base_id"] + table_id = metadata["table_id"] + loader = AirtableLoader(api_key, table_id, base_id) + documents = loader.load() + newDocuments = [ + document.metadata.update({"namespace": document_id}) or document + for document in documents + ] + docs = TextSplitters(newDocuments, text_splitter).document_splitter() + + VectorStoreBase().get_database().from_documents( + docs, embeddings, index_name=INDEX_NAME, namespace=document_id + ) + + if type == "SITEMAP": + filter_urls = metadata["filter_urls"].split(",") + loader = SitemapLoader(sitemap_url=url, filter_urls=filter_urls) + documents = loader.load() + newDocuments = [ + document.metadata.update({"namespace": document_id}) or document + for document in documents + ] + docs = TextSplitters(newDocuments, text_splitter).document_splitter() + + chunk_size = 100 + chunks = chunkify(docs, chunk_size) + + for chunk in chunks: + VectorStoreBase().get_database().from_documents( + chunk, embeddings, index_name=INDEX_NAME, namespace=document_id + ) + + if type == "WEBPAGE": + from llama_index import download_loader + + RemoteDepthReader = download_loader("RemoteDepthReader") + depth = int(metadata["depth"]) + loader = RemoteDepthReader(depth=depth) + documents = loader.load_data(url=url) + langchain_documents = [d.to_langchain_format() for d in documents] + newDocuments = [ + document.metadata.update({"namespace": document_id}) or document + for document in langchain_documents + ] + docs = TextSplitters(newDocuments, text_splitter).document_splitter() + chunk_size = 100 + chunks = chunkify(docs, chunk_size) + + for chunk in chunks: + VectorStoreBase().get_database().from_documents( + chunk, embeddings, index_name=INDEX_NAME, namespace=document_id + ) + if type == "TXT": file_response = content if content is None: diff --git a/app/lib/loaders/__init__.py b/app/lib/loaders/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/app/lib/loaders/sitemap.py b/app/lib/loaders/sitemap.py new file mode 100644 index 000000000..979fdca5a --- /dev/null +++ b/app/lib/loaders/sitemap.py @@ -0,0 +1,65 @@ +import re +import requests +from xml.etree import ElementTree +from bs4 import BeautifulSoup +from langchain.schema import Document + + +class SitemapLoader: + SITEMAP_NAMESPACE = "{http://www.sitemaps.org/schemas/sitemap/0.9}" + + def __init__(self, sitemap_url, filter_urls=None): + self.sitemap_url = sitemap_url + self.filter_urls = filter_urls if filter_urls else [] + + def fetch(self, url): + """Fetch content of a URL using requests.""" + response = requests.get(url) + response.raise_for_status() # Raise exception for HTTP errors + return response.text + + def fetch_text(self, url): + response = requests.get(url) + response.raise_for_status() # Raise exception for HTTP errors + soup = BeautifulSoup(response.content, "html.parser") + raw_text = soup.get_text(separator=" ").strip() + cleaned_text = re.sub(r"\s+", " ", raw_text) + + return cleaned_text + + def matches_any_pattern(self, url): + """Check if the URL matches any of the given patterns.""" + is_match = any(re.search(pattern, url) for pattern in self.filter_urls) + + if is_match: + print(f"Matched URL: {url}") + + return is_match + + def fetch_sitemap_urls(self): + """Fetch URLs from a sitemap.xml file and filter based on patterns.""" + sitemap_content = self.fetch(self.sitemap_url) + + # Parse XML content + root = ElementTree.fromstring(sitemap_content) + urls = [ + url_element.text + for url_element in root.findall( + f"{self.SITEMAP_NAMESPACE}url/{self.SITEMAP_NAMESPACE}loc" + ) + ] + + # Filter URLs + if self.filter_urls: + urls = [url for url in urls if self.matches_any_pattern(url)] + + return urls + + def load(self): + """Fetch content of each URL listed in a sitemap.xml file.""" + urls = self.fetch_sitemap_urls() + + return [ + Document(page_content=self.fetch_text(url), metadata={"url": url}) + for url in urls + ] diff --git a/poetry.lock b/poetry.lock index 76f16e319..0ea5d644f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -122,6 +122,74 @@ files = [ [package.dependencies] frozenlist = ">=1.1.0" +[[package]] +name = "airbyte-cdk" +version = "0.50.2" +description = "A framework for writing Airbyte Connectors." +optional = false +python-versions = ">=3.8" +files = [ + {file = "airbyte-cdk-0.50.2.tar.gz", hash = "sha256:de765e720e446758b436aff360c36c33f8b085be46267422d1a4b743d3da1566"}, + {file = "airbyte_cdk-0.50.2-py3-none-any.whl", hash = "sha256:d0a304fd6659ea36391e88f6a6c7056af1ca149922ae97c2662656f05767d793"}, +] + +[package.dependencies] +airbyte-protocol-models = "0.4.0" +backoff = "*" +cachetools = "*" +Deprecated = ">=1.2,<2.0" +dpath = ">=2.0.1,<2.1.0" +genson = "1.2.2" +isodate = ">=0.6.1,<0.7.0" +Jinja2 = ">=3.1.2,<3.2.0" +jsonref = ">=0.2,<1.0" +jsonschema = ">=3.2.0,<3.3.0" +pendulum = "*" +pydantic = ">=1.9.2,<2.0.0" +python-dateutil = "*" +PyYAML = ">=6.0.1" +requests = "*" +requests-cache = "*" +wcmatch = "8.4" + +[package.extras] +dev = ["avro (>=1.11.2,<1.12.0)", "fastavro (>=1.8.0,<1.9.0)", "freezegun", "mypy", "pandas (==2.0.3)", "pyarrow (==12.0.1)", "pytest", "pytest-cov", "pytest-httpserver", "pytest-mock", "requests-mock"] +file-based = ["avro (>=1.11.2,<1.12.0)", "fastavro (>=1.8.0,<1.9.0)", "pyarrow (==12.0.1)"] +sphinx-docs = ["Sphinx (>=4.2,<5.0)", "sphinx-rtd-theme (>=1.0,<2.0)"] + +[[package]] +name = "airbyte-protocol-models" +version = "0.4.0" +description = "Declares the Airbyte Protocol." +optional = false +python-versions = ">=3.8" +files = [ + {file = "airbyte_protocol_models-0.4.0-py3-none-any.whl", hash = "sha256:e6a31fcd237504198a678d02c0040a8798f281c39203da61a5abce67842c5360"}, + {file = "airbyte_protocol_models-0.4.0.tar.gz", hash = "sha256:518736015c29ac60b6b8964a1b0d9b52e40020bcbd89e2545cc781f0b37d0f2b"}, +] + +[package.dependencies] +pydantic = ">=1.9.2,<2.0.0" + +[[package]] +name = "airbyte-source-stripe" +version = "3.17.1" +description = "Source implementation for Stripe." +optional = false +python-versions = "*" +files = [ + {file = "airbyte-source-stripe-3.17.1.tar.gz", hash = "sha256:7c653ae0ed788766e706db7a8d35a25e3602c9af15d52895724738154b48780e"}, + {file = "airbyte_source_stripe-3.17.1-py3-none-any.whl", hash = "sha256:8cf99fd3095bc726fdbf6ca82d39c3ce4d426349329c8c1ce684ef7ef2f059af"}, +] + +[package.dependencies] +airbyte-cdk = "*" +pendulum = "2.1.2" +stripe = "2.56.0" + +[package.extras] +tests = ["connector-acceptance-test", "pytest (>=6.1,<7.0)", "requests-mock", "requests-mock (>=1.8,<2.0)"] + [[package]] name = "anyio" version = "3.6.2" @@ -383,6 +451,17 @@ d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] +[[package]] +name = "bracex" +version = "2.3.post1" +description = "Bash style brace expander." +optional = false +python-versions = ">=3.7" +files = [ + {file = "bracex-2.3.post1-py3-none-any.whl", hash = "sha256:351b7f20d56fb9ea91f9b9e9e7664db466eb234188c175fd943f8f755c807e73"}, + {file = "bracex-2.3.post1.tar.gz", hash = "sha256:e7b23fc8b2cd06d3dec0692baabecb249dda94e06a617901ff03a6c56fd71693"}, +] + [[package]] name = "bs4" version = "0.0.1" @@ -407,6 +486,31 @@ files = [ {file = "cachetools-5.3.1.tar.gz", hash = "sha256:dce83f2d9b4e1f732a8cd44af8e8fab2dbe46201467fc98b3ef8f269092bf62b"}, ] +[[package]] +name = "cattrs" +version = "23.1.2" +description = "Composable complex class support for attrs and dataclasses." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cattrs-23.1.2-py3-none-any.whl", hash = "sha256:b2bb14311ac17bed0d58785e5a60f022e5431aca3932e3fc5cc8ed8639de50a4"}, + {file = "cattrs-23.1.2.tar.gz", hash = "sha256:db1c821b8c537382b2c7c66678c3790091ca0275ac486c76f3c8f3920e83c657"}, +] + +[package.dependencies] +attrs = ">=20" +exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} +typing_extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} + +[package.extras] +bson = ["pymongo (>=4.2.0,<5.0.0)"] +cbor2 = ["cbor2 (>=5.4.6,<6.0.0)"] +msgpack = ["msgpack (>=1.0.2,<2.0.0)"] +orjson = ["orjson (>=3.5.2,<4.0.0)"] +pyyaml = ["PyYAML (>=6.0,<7.0)"] +tomlkit = ["tomlkit (>=0.11.4,<0.12.0)"] +ujson = ["ujson (>=5.4.0,<6.0.0)"] + [[package]] name = "certifi" version = "2023.5.7" @@ -747,6 +851,17 @@ idna = ["idna (>=2.1,<4.0)"] trio = ["trio (>=0.14,<0.23)"] wmi = ["wmi (>=1.5.1,<2.0.0)"] +[[package]] +name = "dpath" +version = "2.0.8" +description = "Filesystem-like pathing and searching for dictionaries" +optional = false +python-versions = ">=3.7" +files = [ + {file = "dpath-2.0.8-py3-none-any.whl", hash = "sha256:f92f595214dd93a00558d75d4b858beee519f4cffca87f02616ad6cd013f3436"}, + {file = "dpath-2.0.8.tar.gz", hash = "sha256:a3440157ebe80d0a3ad794f1b61c571bef125214800ffdb9afc9424e8250fe9b"}, +] + [[package]] name = "email-validator" version = "2.0.0.post2" @@ -773,6 +888,20 @@ files = [ {file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"}, ] +[[package]] +name = "exceptiongroup" +version = "1.1.2" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.1.2-py3-none-any.whl", hash = "sha256:e346e69d186172ca7cf029c8c1d16235aa0e04035e5750b4b95039e65204328f"}, + {file = "exceptiongroup-1.1.2.tar.gz", hash = "sha256:12c3e887d6485d16943a309616de20ae5582633e0a2eda17f4e10fd61c1e8af5"}, +] + +[package.extras] +test = ["pytest (>=6)"] + [[package]] name = "fastapi" version = "0.95.1" @@ -952,6 +1081,16 @@ smb = ["smbprotocol"] ssh = ["paramiko"] tqdm = ["tqdm"] +[[package]] +name = "genson" +version = "1.2.2" +description = "GenSON is a powerful, user-friendly JSON Schema generator." +optional = false +python-versions = "*" +files = [ + {file = "genson-1.2.2.tar.gz", hash = "sha256:8caf69aa10af7aee0e1a1351d1d06801f4696e005f06cedef438635384346a16"}, +] + [[package]] name = "gitdb" version = "4.0.10" @@ -1436,6 +1575,31 @@ docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker perf = ["ipython"] testing = ["flake8 (<5)", "flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)"] +[[package]] +name = "inflection" +version = "0.5.1" +description = "A port of Ruby on Rails inflector to Python" +optional = false +python-versions = ">=3.5" +files = [ + {file = "inflection-0.5.1-py2.py3-none-any.whl", hash = "sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"}, + {file = "inflection-0.5.1.tar.gz", hash = "sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417"}, +] + +[[package]] +name = "isodate" +version = "0.6.1" +description = "An ISO 8601 date/time/duration parser and formatter" +optional = false +python-versions = "*" +files = [ + {file = "isodate-0.6.1-py2.py3-none-any.whl", hash = "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96"}, + {file = "isodate-0.6.1.tar.gz", hash = "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9"}, +] + +[package.dependencies] +six = "*" + [[package]] name = "itsdangerous" version = "2.1.2" @@ -1490,15 +1654,47 @@ files = [ {file = "joblib-1.2.0.tar.gz", hash = "sha256:e1cee4a79e4af22881164f218d4311f60074197fb707e082e803b61f6d137018"}, ] +[[package]] +name = "jsonref" +version = "0.3.0" +description = "jsonref is a library for automatic dereferencing of JSON Reference objects for Python." +optional = false +python-versions = ">=3.3,<4.0" +files = [ + {file = "jsonref-0.3.0-py3-none-any.whl", hash = "sha256:9480ad1b500f7e795daeb0ef29f9c55ae3a9ab38fb8d6659b6f4868acb5a5bc8"}, + {file = "jsonref-0.3.0.tar.gz", hash = "sha256:68b330c6815dc0d490dbb3d65ccda265ddde9f7856fd2f3322f971d456ea7549"}, +] + +[[package]] +name = "jsonschema" +version = "3.2.0" +description = "An implementation of JSON Schema validation for Python" +optional = false +python-versions = "*" +files = [ + {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"}, + {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, +] + +[package.dependencies] +attrs = ">=17.4.0" +pyrsistent = ">=0.14.0" +setuptools = "*" +six = ">=1.11.0" + +[package.extras] +format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"] +format-nongpl = ["idna", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "webcolors"] + [[package]] name = "langchain" -version = "0.0.252" +version = "0.0.260" description = "Building applications with LLMs through composability" optional = false python-versions = ">=3.8.1,<4.0" files = [ - {file = "langchain-0.0.252-py3-none-any.whl", hash = "sha256:fb14d2346f453656e8f6c138da9123918796ae950a26606c91c156f2e5c0f8c4"}, - {file = "langchain-0.0.252.tar.gz", hash = "sha256:1d205417793bbf9410e0eb23d14b5ce6f13946ccf26a05dcc19bb10def92b03d"}, + {file = "langchain-0.0.260-py3-none-any.whl", hash = "sha256:5ad68342bde9bc5d4590bb042c3fbcfdf800053750e38b621e7f26c88a15e3dc"}, + {file = "langchain-0.0.260.tar.gz", hash = "sha256:e9a5c3dca51806b7829abaf8406cb9b17a8ab8fba05ffc6350ef92a813b5db8d"}, ] [package.dependencies] @@ -1522,11 +1718,12 @@ clarifai = ["clarifai (>=9.1.0)"] cohere = ["cohere (>=4,<5)"] docarray = ["docarray[hnswlib] (>=0.32.0,<0.33.0)"] embeddings = ["sentence-transformers (>=2,<3)"] -extended-testing = ["atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.0.7,<0.0.8)", "chardet (>=5.1.0,<6.0.0)", "esprima (>=4.0.1,<5.0.0)", "feedparser (>=6.0.10,<7.0.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "gql (>=3.4.1,<4.0.0)", "html2text (>=2020.1.16,<2021.0.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "lxml (>=4.9.2,<5.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "openai (>=0,<1)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "tqdm (>=4.48.0)", "xinference (>=0.0.6,<0.0.7)", "zep-python (>=0.32)"] +extended-testing = ["amazon-textract-caller (<2)", "atlassian-python-api (>=3.36.0,<4.0.0)", "beautifulsoup4 (>=4,<5)", "bibtexparser (>=1.4.0,<2.0.0)", "cassio (>=0.0.7,<0.0.8)", "chardet (>=5.1.0,<6.0.0)", "esprima (>=4.0.1,<5.0.0)", "feedparser (>=6.0.10,<7.0.0)", "geopandas (>=0.13.1,<0.14.0)", "gitpython (>=3.1.32,<4.0.0)", "gql (>=3.4.1,<4.0.0)", "html2text (>=2020.1.16,<2021.0.0)", "jinja2 (>=3,<4)", "jq (>=1.4.1,<2.0.0)", "lxml (>=4.9.2,<5.0.0)", "mwparserfromhell (>=0.6.4,<0.7.0)", "mwxml (>=0.3.3,<0.4.0)", "newspaper3k (>=0.2.8,<0.3.0)", "openai (>=0,<1)", "pandas (>=2.0.1,<3.0.0)", "pdfminer-six (>=20221105,<20221106)", "pgvector (>=0.1.6,<0.2.0)", "psychicapi (>=0.8.0,<0.9.0)", "py-trello (>=0.19.0,<0.20.0)", "pymupdf (>=1.22.3,<2.0.0)", "pypdf (>=3.4.0,<4.0.0)", "pypdfium2 (>=4.10.0,<5.0.0)", "pyspark (>=3.4.0,<4.0.0)", "rank-bm25 (>=0.2.2,<0.3.0)", "rapidfuzz (>=3.1.1,<4.0.0)", "requests-toolbelt (>=1.0.0,<2.0.0)", "scikit-learn (>=1.2.2,<2.0.0)", "streamlit (>=1.18.0,<2.0.0)", "sympy (>=1.12,<2.0)", "telethon (>=1.28.5,<2.0.0)", "tqdm (>=4.48.0)", "xata (>=1.0.0a7,<2.0.0)", "xinference (>=0.0.6,<0.0.7)", "xmltodict (>=0.13.0,<0.14.0)", "zep-python (>=0.32)"] javascript = ["esprima (>=4.0.1,<5.0.0)"] llms = ["anthropic (>=0.3,<0.4)", "clarifai (>=9.1.0)", "cohere (>=4,<5)", "huggingface_hub (>=0,<1)", "manifest-ml (>=0.0.1,<0.0.2)", "nlpcloud (>=1,<2)", "openai (>=0,<1)", "openllm (>=0.1.19)", "openlm (>=0.0.5,<0.0.6)", "torch (>=1,<3)", "transformers (>=4,<5)", "xinference (>=0.0.6,<0.0.7)"] openai = ["openai (>=0,<1)", "tiktoken (>=0.3.2,<0.4.0)"] qdrant = ["qdrant-client (>=1.3.1,<2.0.0)"] +scheduled-testing = ["openai (>=0,<1)"] text-helpers = ["chardet (>=5.1.0,<6.0.0)"] [[package]] @@ -2344,6 +2541,40 @@ files = [ Pillow = ">=9.1" Wand = ">=0.6.10" +[[package]] +name = "pendulum" +version = "2.1.2" +description = "Python datetimes made easy" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" +files = [ + {file = "pendulum-2.1.2-cp27-cp27m-macosx_10_15_x86_64.whl", hash = "sha256:b6c352f4bd32dff1ea7066bd31ad0f71f8d8100b9ff709fb343f3b86cee43efe"}, + {file = "pendulum-2.1.2-cp27-cp27m-win_amd64.whl", hash = "sha256:318f72f62e8e23cd6660dbafe1e346950281a9aed144b5c596b2ddabc1d19739"}, + {file = "pendulum-2.1.2-cp35-cp35m-macosx_10_15_x86_64.whl", hash = "sha256:0731f0c661a3cb779d398803655494893c9f581f6488048b3fb629c2342b5394"}, + {file = "pendulum-2.1.2-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:3481fad1dc3f6f6738bd575a951d3c15d4b4ce7c82dce37cf8ac1483fde6e8b0"}, + {file = "pendulum-2.1.2-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:9702069c694306297ed362ce7e3c1ef8404ac8ede39f9b28b7c1a7ad8c3959e3"}, + {file = "pendulum-2.1.2-cp35-cp35m-win_amd64.whl", hash = "sha256:fb53ffa0085002ddd43b6ca61a7b34f2d4d7c3ed66f931fe599e1a531b42af9b"}, + {file = "pendulum-2.1.2-cp36-cp36m-macosx_10_15_x86_64.whl", hash = "sha256:c501749fdd3d6f9e726086bf0cd4437281ed47e7bca132ddb522f86a1645d360"}, + {file = "pendulum-2.1.2-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:c807a578a532eeb226150d5006f156632df2cc8c5693d778324b43ff8c515dd0"}, + {file = "pendulum-2.1.2-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:2d1619a721df661e506eff8db8614016f0720ac171fe80dda1333ee44e684087"}, + {file = "pendulum-2.1.2-cp36-cp36m-win_amd64.whl", hash = "sha256:f888f2d2909a414680a29ae74d0592758f2b9fcdee3549887779cd4055e975db"}, + {file = "pendulum-2.1.2-cp37-cp37m-macosx_10_15_x86_64.whl", hash = "sha256:e95d329384717c7bf627bf27e204bc3b15c8238fa8d9d9781d93712776c14002"}, + {file = "pendulum-2.1.2-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4c9c689747f39d0d02a9f94fcee737b34a5773803a64a5fdb046ee9cac7442c5"}, + {file = "pendulum-2.1.2-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:1245cd0075a3c6d889f581f6325dd8404aca5884dea7223a5566c38aab94642b"}, + {file = "pendulum-2.1.2-cp37-cp37m-win_amd64.whl", hash = "sha256:db0a40d8bcd27b4fb46676e8eb3c732c67a5a5e6bfab8927028224fbced0b40b"}, + {file = "pendulum-2.1.2-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:f5e236e7730cab1644e1b87aca3d2ff3e375a608542e90fe25685dae46310116"}, + {file = "pendulum-2.1.2-cp38-cp38-manylinux1_i686.whl", hash = "sha256:de42ea3e2943171a9e95141f2eecf972480636e8e484ccffaf1e833929e9e052"}, + {file = "pendulum-2.1.2-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7c5ec650cb4bec4c63a89a0242cc8c3cebcec92fcfe937c417ba18277d8560be"}, + {file = "pendulum-2.1.2-cp38-cp38-win_amd64.whl", hash = "sha256:33fb61601083f3eb1d15edeb45274f73c63b3c44a8524703dc143f4212bf3269"}, + {file = "pendulum-2.1.2-cp39-cp39-manylinux1_i686.whl", hash = "sha256:29c40a6f2942376185728c9a0347d7c0f07905638c83007e1d262781f1e6953a"}, + {file = "pendulum-2.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:94b1fc947bfe38579b28e1cccb36f7e28a15e841f30384b5ad6c5e31055c85d7"}, + {file = "pendulum-2.1.2.tar.gz", hash = "sha256:b06a0ca1bfe41c990bbf0c029f0b6501a7f2ec4e38bfec730712015e8860f207"}, +] + +[package.dependencies] +python-dateutil = ">=2.6,<3.0" +pytzdata = ">=2020.1" + [[package]] name = "pillow" version = "9.5.0" @@ -2586,6 +2817,24 @@ files = [ [package.dependencies] requests = "*" +[[package]] +name = "pyairtable" +version = "2.0.0" +description = "Python Client for the Airtable API" +optional = false +python-versions = "*" +files = [ + {file = "pyairtable-2.0.0-py2.py3-none-any.whl", hash = "sha256:41e256fdcf0e68e8348197d8f474b0789b6f05d2fd3bd6c407e5900a8fae02f8"}, + {file = "pyairtable-2.0.0.tar.gz", hash = "sha256:dd2dde960f261f947407cf865922bcf2e0e2524b94c7a596c8901dfdf4471b68"}, +] + +[package.dependencies] +inflection = "*" +pydantic = ">=1.10,<2.0" +requests = ">=2.22.0" +typing-extensions = "*" +urllib3 = ">=1.26" + [[package]] name = "pyasn1" version = "0.5.0" @@ -2805,6 +3054,42 @@ docs = ["myst_parser", "sphinx", "sphinx_rtd_theme"] full = ["Pillow", "PyCryptodome"] image = ["Pillow"] +[[package]] +name = "pyrsistent" +version = "0.19.3" +description = "Persistent/Functional/Immutable data structures" +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyrsistent-0.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a"}, + {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64"}, + {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf"}, + {file = "pyrsistent-0.19.3-cp310-cp310-win32.whl", hash = "sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a"}, + {file = "pyrsistent-0.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da"}, + {file = "pyrsistent-0.19.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9"}, + {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393"}, + {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19"}, + {file = "pyrsistent-0.19.3-cp311-cp311-win32.whl", hash = "sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3"}, + {file = "pyrsistent-0.19.3-cp311-cp311-win_amd64.whl", hash = "sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-win32.whl", hash = "sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1"}, + {file = "pyrsistent-0.19.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b"}, + {file = "pyrsistent-0.19.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8"}, + {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a"}, + {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c"}, + {file = "pyrsistent-0.19.3-cp38-cp38-win32.whl", hash = "sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c"}, + {file = "pyrsistent-0.19.3-cp38-cp38-win_amd64.whl", hash = "sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7"}, + {file = "pyrsistent-0.19.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc"}, + {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2"}, + {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3"}, + {file = "pyrsistent-0.19.3-cp39-cp39-win32.whl", hash = "sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2"}, + {file = "pyrsistent-0.19.3-cp39-cp39-win_amd64.whl", hash = "sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98"}, + {file = "pyrsistent-0.19.3-py3-none-any.whl", hash = "sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64"}, + {file = "pyrsistent-0.19.3.tar.gz", hash = "sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440"}, +] + [[package]] name = "python-dateutil" version = "2.8.2" @@ -2925,6 +3210,17 @@ files = [ {file = "pytz-2023.3.tar.gz", hash = "sha256:1d8ce29db189191fb55338ee6d0387d82ab59f3d00eac103412d64e0ebd0c588"}, ] +[[package]] +name = "pytzdata" +version = "2020.1" +description = "The Olson timezone database for Python." +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pytzdata-2020.1-py2.py3-none-any.whl", hash = "sha256:e1e14750bcf95016381e4d472bad004eef710f2d6417240904070b3d6654485f"}, + {file = "pytzdata-2020.1.tar.gz", hash = "sha256:3efa13b335a00a8de1d345ae41ec78dd11c9f8807f522d39850f2dd828681540"}, +] + [[package]] name = "pywin32" version = "306" @@ -2950,51 +3246,51 @@ files = [ [[package]] name = "pyyaml" -version = "6.0" +version = "6.0.1" description = "YAML parser and emitter for Python" optional = false python-versions = ">=3.6" files = [ - {file = "PyYAML-6.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53"}, - {file = "PyYAML-6.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b"}, - {file = "PyYAML-6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"}, - {file = "PyYAML-6.0-cp310-cp310-win32.whl", hash = "sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513"}, - {file = "PyYAML-6.0-cp310-cp310-win_amd64.whl", hash = "sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358"}, - {file = "PyYAML-6.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f"}, - {file = "PyYAML-6.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782"}, - {file = "PyYAML-6.0-cp311-cp311-win32.whl", hash = "sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7"}, - {file = "PyYAML-6.0-cp311-cp311-win_amd64.whl", hash = "sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf"}, - {file = "PyYAML-6.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92"}, - {file = "PyYAML-6.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4"}, - {file = "PyYAML-6.0-cp36-cp36m-win32.whl", hash = "sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293"}, - {file = "PyYAML-6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57"}, - {file = "PyYAML-6.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4"}, - {file = "PyYAML-6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9"}, - {file = "PyYAML-6.0-cp37-cp37m-win32.whl", hash = "sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737"}, - {file = "PyYAML-6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d"}, - {file = "PyYAML-6.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34"}, - {file = "PyYAML-6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287"}, - {file = "PyYAML-6.0-cp38-cp38-win32.whl", hash = "sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78"}, - {file = "PyYAML-6.0-cp38-cp38-win_amd64.whl", hash = "sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b"}, - {file = "PyYAML-6.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3"}, - {file = "PyYAML-6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0"}, - {file = "PyYAML-6.0-cp39-cp39-win32.whl", hash = "sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb"}, - {file = "PyYAML-6.0-cp39-cp39-win_amd64.whl", hash = "sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c"}, - {file = "PyYAML-6.0.tar.gz", hash = "sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] [[package]] @@ -3134,6 +3430,36 @@ urllib3 = ">=1.21.1,<1.27" socks = ["PySocks (>=1.5.6,!=1.5.7)"] use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] +[[package]] +name = "requests-cache" +version = "1.1.0" +description = "A persistent cache for python requests" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "requests_cache-1.1.0-py3-none-any.whl", hash = "sha256:178282bce704b912c59e7f88f367c42bddd6cde6bf511b2a3e3cfb7e5332a92a"}, + {file = "requests_cache-1.1.0.tar.gz", hash = "sha256:41b79166aa8e300cc4de982f7ab7c52af914a785160be1eda25c6e9265969a67"}, +] + +[package.dependencies] +attrs = ">=21.2" +cattrs = ">=22.2" +platformdirs = ">=2.5" +requests = ">=2.22" +url-normalize = ">=1.4" +urllib3 = ">=1.25.5" + +[package.extras] +all = ["boto3 (>=1.15)", "botocore (>=1.18)", "itsdangerous (>=2.0)", "pymongo (>=3)", "pyyaml (>=5.4)", "redis (>=3)", "ujson (>=5.4)"] +bson = ["bson (>=0.5)"] +docs = ["furo (>=2023.3,<2024.0)", "linkify-it-py (>=2.0,<3.0)", "myst-parser (>=1.0,<2.0)", "sphinx (>=5.0.2,<6.0.0)", "sphinx-autodoc-typehints (>=1.19)", "sphinx-automodapi (>=0.14)", "sphinx-copybutton (>=0.5)", "sphinx-design (>=0.2)", "sphinx-notfound-page (>=0.8)", "sphinxcontrib-apidoc (>=0.3)", "sphinxext-opengraph (>=0.6)"] +dynamodb = ["boto3 (>=1.15)", "botocore (>=1.18)"] +json = ["ujson (>=5.4)"] +mongodb = ["pymongo (>=3)"] +redis = ["redis (>=3)"] +security = ["itsdangerous (>=2.0)"] +yaml = ["pyyaml (>=5.4)"] + [[package]] name = "requests-oauthlib" version = "1.3.1" @@ -3471,6 +3797,20 @@ typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\"" [package.extras] full = ["httpx (>=0.22.0)", "itsdangerous", "jinja2", "python-multipart", "pyyaml"] +[[package]] +name = "stripe" +version = "2.56.0" +description = "Python bindings for the Stripe API" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "stripe-2.56.0-py2.py3-none-any.whl", hash = "sha256:6c685eeadf9e3608315b6d84b4f5f2da2909179b65633ce20f296be22ed21a98"}, + {file = "stripe-2.56.0.tar.gz", hash = "sha256:2ff904fb8dee0d25f135059468a876852d24dc8cbe0b45d7aff56a028045777c"}, +] + +[package.dependencies] +requests = {version = ">=2.20", markers = "python_version >= \"3.0\""} + [[package]] name = "superagent-py" version = "0.0.29" @@ -3907,6 +4247,20 @@ s3 = ["fsspec", "s3fs"] slack = ["slack-sdk"] wikipedia = ["wikipedia"] +[[package]] +name = "url-normalize" +version = "1.4.3" +description = "URL normalization for Python" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" +files = [ + {file = "url-normalize-1.4.3.tar.gz", hash = "sha256:d23d3a070ac52a67b83a1c59a0e68f8608d1cd538783b401bc9de2c0fac999b2"}, + {file = "url_normalize-1.4.3-py2.py3-none-any.whl", hash = "sha256:ec3c301f04e5bb676d333a7fa162fa977ad2ca04b7e652bfc9fac4e405728eed"}, +] + +[package.dependencies] +six = "*" + [[package]] name = "urllib3" version = "1.26.15" @@ -4065,6 +4419,20 @@ files = [ [package.dependencies] anyio = ">=3.0.0" +[[package]] +name = "wcmatch" +version = "8.4" +description = "Wildcard/glob file name matcher." +optional = false +python-versions = ">=3.7" +files = [ + {file = "wcmatch-8.4-py3-none-any.whl", hash = "sha256:dc7351e5a7f8bbf4c6828d51ad20c1770113f5f3fd3dfe2a03cfde2a63f03f98"}, + {file = "wcmatch-8.4.tar.gz", hash = "sha256:ba4fc5558f8946bf1ffc7034b05b814d825d694112499c86035e0e4d398b6a67"}, +] + +[package.dependencies] +bracex = ">=2.1.1" + [[package]] name = "websockets" version = "11.0.3" @@ -4392,4 +4760,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "3b0241a216ca44aa311cad1e14422cfcfa31f6ea983e4cb08147cbd31daa54e5" +content-hash = "2c070bb153ca9335ee343655ed8bd43f4d3e189a4d2600858769b07faa915372" diff --git a/prisma/migrations/20230808152201_document_webpage/migration.sql b/prisma/migrations/20230808152201_document_webpage/migration.sql new file mode 100644 index 000000000..86878b2c8 --- /dev/null +++ b/prisma/migrations/20230808152201_document_webpage/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "DocumentType" ADD VALUE 'WEBPAGE'; diff --git a/prisma/migrations/20230809165852_document_stripe/migration.sql b/prisma/migrations/20230809165852_document_stripe/migration.sql new file mode 100644 index 000000000..ae76a3f85 --- /dev/null +++ b/prisma/migrations/20230809165852_document_stripe/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "DocumentType" ADD VALUE 'STRIPE'; diff --git a/prisma/migrations/20230809204553_document_airtable/migration.sql b/prisma/migrations/20230809204553_document_airtable/migration.sql new file mode 100644 index 000000000..2e4592703 --- /dev/null +++ b/prisma/migrations/20230809204553_document_airtable/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "DocumentType" ADD VALUE 'AIRTABLE'; diff --git a/prisma/migrations/20230810071652_document_sitemap/migration.sql b/prisma/migrations/20230810071652_document_sitemap/migration.sql new file mode 100644 index 000000000..9f30b7285 --- /dev/null +++ b/prisma/migrations/20230810071652_document_sitemap/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "DocumentType" ADD VALUE 'SITEMAP'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 46ca55609..077b8d7d7 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -31,6 +31,10 @@ enum DocumentType { FIRESTORE PSYCHIC GITHUB_REPOSITORY + WEBPAGE + STRIPE + AIRTABLE + SITEMAP } enum ToolType { diff --git a/pyproject.toml b/pyproject.toml index 52fd9cf23..56ef2ff26 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,8 +49,10 @@ colorlog = "^6.7.0" llama-hub = "^0.0.16" pygithub = "^1.59.0" gitpython = "^3.1.32" -langchain = "^0.0.252" vulture = "^2.7" +airbyte-source-stripe = "^3.17.1" +pyairtable = "^2.0.0" +langchain = "^0.0.260" [build-system] diff --git a/ui/.env.example b/ui/.env.example index 9cfbe7039..c3e38440a 100644 --- a/ui/.env.example +++ b/ui/.env.example @@ -1,10 +1,16 @@ NEXTAUTH_URL="http://localhost:3000" NEXTAUTH_SECRET="superagent" NEXT_PUBLIC_SUPERAGENT_API_URL="http://127.0.0.1:8080/api/v1" +# Mandatory for uploading files +NEXT_PUBLIC_AWS_S3_BUCKET= +NEXT_PUBLIC_AWS_S3_REGION= +NEXT_PUBLIC_AMAZON_S3_ACCESS_KEY_ID= +NEXT_PUBLIC_AMAZON_S3_SECRET_ACCESS_KEY= + # Shareable Key Needs to be exactly 16 characters, do not wrap value in quotes NEXT_PUBLIC_SHARABLE_KEY_SECRET= -# Optiona OAuth values +# Optional OAuth values NEXT_PUBLIC_GITHUB_CLIENT_ID="" NEXT_PUBLIC_GITHUB_CLIENT_SECRET="" NEXT_PUBLIC_GOOGLE_CLIENT_ID="" diff --git a/ui/app/_components/search-bar.js b/ui/app/_components/search-bar.js index e7c3c33af..9375ea069 100644 --- a/ui/app/_components/search-bar.js +++ b/ui/app/_components/search-bar.js @@ -47,7 +47,7 @@ export default function SearchBar({ onSearch, onReset }) { {...register("searchTerm", { required: true })} /> - {searchTerm && ( + {searchTerm && onReset && ( <> + + Import data from Intercom, Hubspot, Zendesk, Jira, Notion and many + more... + + + + + handleSearch(values)} + onReset={() => setData(data)} + /> + + + + + + + + + + + + + {filteredData + ? filteredData + ?.filter(({ type }) => + ACCEPTABLE_APPLICATION_TYPES.includes(type) + ) + .map(({ id, name, createdAt, type, url }) => ( + handleDelete(id)} + onEdit={(id) => handleEdit(id)} + /> + )) + : data + ?.filter(({ type }) => + ACCEPTABLE_APPLICATION_TYPES.includes(type) + ) + .map(({ id, name, createdAt, type, url }) => ( + handleDelete(id)} + onEdit={(id) => handleEdit(id)} + /> + ))} + +
NameTypeIDCreated at 
+
+
+ + + + + {selectedSource?.name || "Connect application"} + + + + + {selectedSource ? ( + + + + Name + + + + Description + + + Use this to instruct the Agent when to query this + source. + + + {!selectedDocument && + selectedSource.inputs.map( + ({ key, name, type, required, options, helpText }) => ( + + {name} + {type === "input" && ( + + )} + {type === "date" && ( + + )} + {type === "select" && } + {helpText && ( + {helpText} + )} + + ) + )} + + {selectedSource && ( + + + + + )} + + ) : ( + + handleAppSearch(values)} /> + + {filteredAppData + ? filteredAppData.map(({ name, logo, id, is_live }) => ( + + + + + {name} + + + + + + + )) + : APPLICATIONS.map(({ name, logo, id, is_live }) => ( + + + + + {name} + + + + + + + ))} + + + )} + + + + + + ); +} diff --git a/ui/app/documents/document-picker.js b/ui/app/datasources/_components/document-picker.js similarity index 100% rename from ui/app/documents/document-picker.js rename to ui/app/datasources/_components/document-picker.js diff --git a/ui/app/datasources/_components/files.js b/ui/app/datasources/_components/files.js new file mode 100644 index 000000000..22b10fdab --- /dev/null +++ b/ui/app/datasources/_components/files.js @@ -0,0 +1,371 @@ +"use client"; +import { useCallback, useState } from "react"; +import SearchBar from "@/app/_components/search-bar"; +import { useRouter } from "next/navigation"; +import { + Button, + Code, + HStack, + FormControl, + FormLabel, + FormHelperText, + Input, + Spinner, + Icon, + Modal, + ModalBody, + ModalOverlay, + ModalHeader, + ModalContent, + ModalCloseButton, + ModalFooter, + Stack, + Text, + Table, + Thead, + Tbody, + Tr, + Th, + Td, + TableContainer, + IconButton, + useToast, + useDisclosure, + Tag, +} from "@chakra-ui/react"; +import dayjs from "dayjs"; +import { useForm } from "react-hook-form"; +import { TbCopy, TbTrash, TbPencil } from "react-icons/tb"; +import relativeTime from "dayjs/plugin/relativeTime"; +import { useAsyncFn } from "react-use"; +import API from "@/lib/api"; +import UploadButton from "./upload-button"; +import { + ACCEPTABLE_STATIC_FILE_TYPES, + getFileType, + uploadFile, +} from "@/lib/datasources"; + +dayjs.extend(relativeTime); + +function DocumentRow({ id, name, createdAt, type, onDelete, onEdit }) { + const toast = useToast(); + const copyToClipboard = (text) => { + navigator.clipboard.writeText(text); + + toast({ + description: "Copied to clipboard", + position: "top", + colorScheme: "gray", + }); + }; + + const [{ loading: isDeleting }, handleDelete] = useAsyncFn( + async (id) => { + await onDelete(id); + + toast({ + description: "Document deleted", + position: "top", + colorScheme: "gray", + }); + }, + [onDelete] + ); + + return ( + + + + {name} + + + + + {type} + + + + + {id} + } + onClick={() => copyToClipboard(id)} + /> + + + + {dayjs(createdAt).fromNow()} + + + + } + onClick={() => onEdit(id)} + /> + + + ) : ( + + ) + } + onClick={() => handleDelete(id)} + /> + + + + ); +} + +export default function Files({ data, session }) { + const [filteredData, setData] = useState(); + const [isCreatingDocument, setIsCreatingDocument] = useState(); + const { isOpen, onClose, onOpen } = useDisclosure(); + const [selectedDocument, setSelectedDocument] = useState(); + const router = useRouter(); + const api = new API(session); + const toast = useToast(); + const { + formState: { isSubmitting, errors }, + handleSubmit, + register, + reset, + setValue, + } = useForm(); + const onCancel = async () => { + reset(); + setSelectedDocument(); + onClose(); + }; + + const onUpdate = async (values) => { + const { name, description } = values; + const payload = { + name, + description, + }; + + await api.patchDocument(selectedDocument, payload); + + if (process.env.NEXT_PUBLIC_SEGMENT_WRITE_KEY) { + analytics.track("Updated Document", { ...payload }); + } + + toast({ + description: "Document updated", + position: "top", + colorScheme: "gray", + }); + + setData(); + router.refresh(); + reset(); + setSelectedDocument(); + onClose(); + }; + + const handleSearch = ({ searchTerm }) => { + if (!searchTerm) { + setData(data); + } + + const keysToFilter = ["name", "type"]; + const filteredItems = data.filter((item) => + keysToFilter.some((key) => + item[key].toString().toLowerCase().includes(searchTerm.toLowerCase()) + ) + ); + + setData(filteredItems); + }; + + const handleDelete = async (id) => { + await api.deleteDocument({ id }); + + if (process.env.NEXT_PUBLIC_SEGMENT_WRITE_KEY) { + analytics.track("Deleted Document", { id }); + } + + setData(); + router.refresh(); + }; + + const handleEdit = async (documentId) => { + const document = data.find(({ id }) => id === documentId); + + setSelectedDocument(documentId); + setValue("name", document?.name); + setValue("description", document?.description); + onOpen(); + }; + + const onUpload = useCallback(async (file) => { + const type = getFileType(file.type); + const { Location } = await uploadFile(file); + const payload = { + name: file.name, + description: `Useful for answering questions about ${file.name}`, + type, + url: Location, + }; + + await api.createDocument(payload); + + if (process.env.NEXT_PUBLIC_SEGMENT_WRITE_KEY) { + analytics.track("Created Document", { ...payload }); + } + + toast({ + description: "Document created", + position: "top", + colorScheme: "gray", + }); + + router.refresh(); + }, []); + + return ( + + + + + + We currently support .txt, .pdf,{" "} + .csv and .md files + + + + + handleSearch(values)} + onReset={() => setData(data)} + /> + + + + + + + + + + + + + {filteredData + ? filteredData + ?.filter(({ type }) => + ACCEPTABLE_STATIC_FILE_TYPES.includes(type) + ) + .map(({ id, name, createdAt, type, url }) => ( + handleDelete(id)} + onEdit={(id) => handleEdit(id)} + /> + )) + : data + ?.filter(({ type }) => + ACCEPTABLE_STATIC_FILE_TYPES.includes(type) + ) + .map(({ id, name, createdAt, type, url }) => ( + handleDelete(id)} + onEdit={(id) => handleEdit(id)} + /> + ))} + +
NameTypeIDCreated at 
+
+
+ + + + Update document + + + + + + Name + + A document name. + {errors?.name && ( + Invalid name + )} + + + Description + + + What is this document useful for? + + {errors?.description && ( + Invalid description + )} + + + + + + + + + + +
+ ); +} diff --git a/ui/app/datasources/_components/upload-button.js b/ui/app/datasources/_components/upload-button.js new file mode 100644 index 000000000..c0a7178cd --- /dev/null +++ b/ui/app/datasources/_components/upload-button.js @@ -0,0 +1,44 @@ +"use client"; +import { useState, useRef } from "react"; +import { Button, Icon, Input } from "@chakra-ui/react"; +import { TbPlus } from "react-icons/tb"; +import { useAsyncFn } from "react-use"; + +export default function UploadButton({ accept, label, onSelect }) { + const fileInputRef = useRef(null); + const [isLoading, setIsLoading] = useState(); + + const handleFileChange = async (event) => { + const file = event.target.files[0]; + setIsLoading(true); + + if (file) { + await onSelect(file); + setIsLoading(); + } + }; + + const triggerFileInput = () => { + fileInputRef.current.click(); + }; + + return ( + <> + + + + ); +} diff --git a/ui/app/datasources/_components/webpages.js b/ui/app/datasources/_components/webpages.js new file mode 100644 index 000000000..d412d610d --- /dev/null +++ b/ui/app/datasources/_components/webpages.js @@ -0,0 +1,421 @@ +"use client"; +import { useCallback, useState } from "react"; +import SearchBar from "@/app/_components/search-bar"; +import { useRouter } from "next/navigation"; +import { + Button, + Code, + HStack, + FormControl, + FormLabel, + FormHelperText, + Input, + Spinner, + Icon, + Modal, + ModalBody, + ModalOverlay, + ModalHeader, + ModalContent, + ModalCloseButton, + ModalFooter, + Stack, + Text, + Table, + Thead, + Tbody, + Tr, + Th, + Td, + TableContainer, + IconButton, + useToast, + useDisclosure, + Tag, + Alert, + AlertTitle, + AlertDescription, + Checkbox, + Textarea, +} from "@chakra-ui/react"; +import dayjs from "dayjs"; +import { useForm } from "react-hook-form"; +import { TbCopy, TbPlus, TbTrash, TbPencil } from "react-icons/tb"; +import relativeTime from "dayjs/plugin/relativeTime"; +import { useAsyncFn } from "react-use"; +import API from "@/lib/api"; +import { ACCEPTABLE_WEBPAGE_TYPES, isSitemapUrl } from "@/lib/datasources"; + +dayjs.extend(relativeTime); + +function DocumentRow({ id, name, createdAt, type, onDelete, onEdit }) { + const toast = useToast(); + const copyToClipboard = (text) => { + navigator.clipboard.writeText(text); + + toast({ + description: "Copied to clipboard", + position: "top", + colorScheme: "gray", + }); + }; + + const [{ loading: isDeleting }, handleDelete] = useAsyncFn( + async (id) => { + await onDelete(id); + + toast({ + description: "Document deleted", + position: "top", + colorScheme: "gray", + }); + }, + [onDelete] + ); + + return ( + + + + {name} + + + + + {type} + + + + + {id} + } + onClick={() => copyToClipboard(id)} + /> + + + + {dayjs(createdAt).fromNow()} + + + + } + onClick={() => onEdit(id)} + /> + + + ) : ( + + ) + } + onClick={() => handleDelete(id)} + /> + + + + ); +} + +export default function Webpages({ data, session }) { + const [filteredData, setData] = useState(); + const [isCreatingDocument, setIsCreatingDocument] = useState(); + const { isOpen, onClose, onOpen } = useDisclosure(); + const [selectedDocument, setSelectedDocument] = useState(); + const router = useRouter(); + const api = new API(session); + const toast = useToast(); + const { + formState: { isSubmitting, errors }, + handleSubmit, + register, + reset, + setValue, + watch, + } = useForm(); + const url = watch("url"); + + const onCancel = async () => { + reset(); + setSelectedDocument(); + onClose(); + }; + + const onUpdate = async (values) => { + const { name, description } = values; + const payload = { + name, + description, + }; + + await api.patchDocument(selectedDocument, payload); + + if (process.env.NEXT_PUBLIC_SEGMENT_WRITE_KEY) { + analytics.track("Updated Document", { ...payload }); + } + + toast({ + description: "Document updated", + position: "top", + colorScheme: "gray", + }); + + setData(); + router.refresh(); + reset(); + setSelectedDocument(); + onClose(); + }; + + const handleSearch = ({ searchTerm }) => { + if (!searchTerm) { + setData(data); + } + + const keysToFilter = ["name", "type"]; + const filteredItems = data.filter((item) => + keysToFilter.some((key) => + item[key].toString().toLowerCase().includes(searchTerm.toLowerCase()) + ) + ); + + setData(filteredItems); + }; + + const handleDelete = async (id) => { + await api.deleteDocument({ id }); + + if (process.env.NEXT_PUBLIC_SEGMENT_WRITE_KEY) { + analytics.track("Deleted Document", { id }); + } + + setData(); + router.refresh(); + }; + + const handleEdit = async (documentId) => { + const document = data.find(({ id }) => id === documentId); + + setSelectedDocument(documentId); + setValue("name", document?.name); + setValue("description", document?.description); + setValue("url", document?.url); + onOpen(); + }; + + const onSubmit = useCallback(async (values) => { + const { name, description, url, include_subpages, ...metadata } = values; + const type = isSitemapUrl(url) ? "SITEMAP" : "URL"; + const payload = { + name: name, + description, + url, + type, + metadata, + }; + + await api.createDocument(payload); + + if (process.env.NEXT_PUBLIC_SEGMENT_WRITE_KEY) { + analytics.track("Created Webpage", { ...payload }); + } + + toast({ + description: "Webpage created", + position: "top", + colorScheme: "gray", + }); + + onCancel(); + + router.refresh(); + }, []); + + return ( + + + + + + Add any webpage + + + + + handleSearch(values)} + onReset={() => setData(data)} + /> + + + + + + + + + + + + + {filteredData + ? filteredData + ?.filter(({ type }) => + ACCEPTABLE_WEBPAGE_TYPES.includes(type) + ) + .map(({ id, name, createdAt, type, url }) => ( + handleDelete(id)} + onEdit={(id) => handleEdit(id)} + /> + )) + : data + ?.filter(({ type }) => + ACCEPTABLE_WEBPAGE_TYPES.includes(type) + ) + .map(({ id, name, createdAt, type, url }) => ( + handleDelete(id)} + onEdit={(id) => handleEdit(id)} + /> + ))} + +
NameTypeIDCreated at 
+
+
+ + + + + {selectedDocument ? "Update webpage" : "Add webpage"} + + + + + {isSitemapUrl(url) && ( + + + + Looks like you are trying to import a Sitemap? + + + + You can choose to filter out which urls you using the{" "} + Filter URLs field. This is recommended since + Sitemaps can be very large and take a long time to ingest. + + + )} + + + Name + + The name of the webpage. + {errors?.name && ( + Invalid name + )} + + + Description + + + What is this document useful for? + + {errors?.description && ( + Invalid description + )} + + + URL + + Add the webpage URL + {errors?.url && ( + Invalid URL + )} + + {isSitemapUrl(url) && ( + + Filter URLs +