diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 00000000..d01386f2 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: arc53 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index dd0799c6..a41a1f3e 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -8,12 +8,12 @@ updates: - package-ecosystem: "pip" # See documentation for possible values directory: "/application" # Location of package manifests schedule: - interval: "weekly" + interval: "daily" - package-ecosystem: "npm" # See documentation for possible values directory: "/frontend" # Location of package manifests schedule: - interval: "weekly" + interval: "daily" - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "weekly" + interval: "daily" diff --git a/HACKTOBERFEST.md b/HACKTOBERFEST.md deleted file mode 100644 index 8656bd84..00000000 --- a/HACKTOBERFEST.md +++ /dev/null @@ -1,41 +0,0 @@ -# **🎉 Join the Hacktoberfest with DocsGPT and win a Free T-shirt and other prizes! 🎉** - -Welcome, contributors! We're excited to announce that DocsGPT is participating in Hacktoberfest. Get involved by submitting meaningful pull requests. - -All contributors with accepted PRs will receive a cool Holopin! 🤩 (Watch out for a reply in your PR to collect it). - -### 🏆 Top 50 contributors will receive a special T-shirt - -### 🏆 [LLM Document analysis by LexEU competition](https://github.com/arc53/DocsGPT/blob/main/lexeu-competition.md): -A separate competition is available for those who submit new retrieval / workflow method that will analyze a Document using EU laws. -With 200$, 100$, 50$ prize for 1st, 2nd and 3rd place respectively. -You can find more information [here](https://github.com/arc53/DocsGPT/blob/main/lexeu-competition.md) - -## 📜 Here's How to Contribute: -```text -🛠️ Code: This is the golden ticket! Make meaningful contributions through PRs. - -🧩 API extension: Build an app utilising DocsGPT API. We prefer submissions that showcase original ideas and turn the API into an AI agent. -They can be a completely separate repos. -For example: -https://github.com/arc53/tg-bot-docsgpt-extenstion or -https://github.com/arc53/DocsGPT-cli - -Non-Code Contributions: - -📚 Wiki: Improve our documentation, create a guide or change existing documentation. - -🖥️ Design: Improve the UI/UX or design a new feature. - -📝 Blogging or Content Creation: Write articles or create videos to showcase DocsGPT or highlight your contributions! -``` - -### 📝 Guidelines for Pull Requests: -- Familiarize yourself with the current contributions and our [Roadmap](https://github.com/orgs/arc53/projects/2). -- Before contributing we highly advise that you check existing [issues](https://github.com/arc53/DocsGPT/issues) or [create](https://github.com/arc53/DocsGPT/issues/new/choose) an issue and wait to get assigned. -- Once you are finished with your contribution, please fill in this [form](https://airtable.com/appikMaJwdHhC1SDP/pagoblCJ9W29wf6Hf/form). -- Refer to the [Documentation](https://docs.docsgpt.cloud/). -- Feel free to join our [Discord](https://discord.gg/n5BX8dh8rU) server. We're here to help newcomers, so don't hesitate to jump in! Join us [here](https://discord.gg/n5BX8dh8rU). - -Thank you very much for considering contributing to DocsGPT during Hacktoberfest! 🙏 Your contributions (not just simple typos) could earn you a stylish new t-shirt and other prizes as a token of our appreciation. 🎁 Join us, and let's code together! 🚀 - diff --git a/application/api/user/routes.py b/application/api/user/routes.py index f2d1be06..06b60a25 100644 --- a/application/api/user/routes.py +++ b/application/api/user/routes.py @@ -185,8 +185,13 @@ class SubmitFeedback(Resource): ), "answer": fields.String(required=False, description="The AI answer"), "feedback": fields.String(required=True, description="User feedback"), - "question_index":fields.Integer(required=True, description="The question number in that particular conversation"), - "conversation_id":fields.String(required=True, description="id of the particular conversation"), + "question_index": fields.Integer( + required=True, + description="The question number in that particular conversation", + ), + "conversation_id": fields.String( + required=True, description="id of the particular conversation" + ), "api_key": fields.String(description="Optional API key"), }, ) @@ -196,21 +201,24 @@ class SubmitFeedback(Resource): ) def post(self): data = request.get_json() - required_fields = [ "feedback","conversation_id","question_index"] + required_fields = ["feedback", "conversation_id", "question_index"] missing_fields = check_required_fields(data, required_fields) if missing_fields: return missing_fields try: conversations_collection.update_one( - {"_id": ObjectId(data["conversation_id"]), f"queries.{data['question_index']}": {"$exists": True}}, - { - "$set": { - f"queries.{data['question_index']}.feedback": data["feedback"] - } - } - ) - + { + "_id": ObjectId(data["conversation_id"]), + f"queries.{data['question_index']}": {"$exists": True}, + }, + { + "$set": { + f"queries.{data['question_index']}.feedback": data["feedback"] + } + }, + ) + except Exception as err: return make_response(jsonify({"success": False, "error": str(err)}), 400) @@ -253,11 +261,9 @@ class DeleteOldIndexes(Resource): jsonify({"success": False, "message": "Missing required fields"}), 400 ) - doc = sources_collection.find_one( - {"_id": ObjectId(source_id), "user": "local"} - ) + doc = sources_collection.find_one({"_id": ObjectId(source_id), "user": "local"}) if not doc: - return make_response(jsonify({"status": "not found"}), 404) + return make_response(jsonify({"status": "not found"}), 404) try: if settings.VECTOR_STORE == "faiss": shutil.rmtree(os.path.join(current_dir, "indexes", str(doc["_id"]))) @@ -271,7 +277,7 @@ class DeleteOldIndexes(Resource): pass except Exception as err: return make_response(jsonify({"success": False, "error": str(err)}), 400) - + sources_collection.delete_one({"_id": ObjectId(source_id)}) return make_response(jsonify({"success": True}), 200) @@ -498,7 +504,9 @@ class PaginatedSources(Resource): total_documents = sources_collection.count_documents(query) total_pages = max(1, math.ceil(total_documents / rows_per_page)) - page = min(max(1, page), total_pages) # add this to make sure page inbound is within the range + page = min( + max(1, page), total_pages + ) # add this to make sure page inbound is within the range sort_order = 1 if sort_order == "asc" else -1 skip = (page - 1) * rows_per_page @@ -543,7 +551,7 @@ class CombinedJson(Resource): user = "local" data = [ { - "name": "default", + "name": "Default", "date": "default", "model": settings.EMBEDDINGS_NAME, "location": "remote", @@ -2097,5 +2105,4 @@ class DeleteTool(Resource): except Exception as err: return {"success": False, "error": str(err)}, 400 - return {"success": True}, 200 - \ No newline at end of file + return {"success": True}, 200 \ No newline at end of file diff --git a/application/parser/remote/crawler_loader.py b/application/parser/remote/crawler_loader.py index 76325ae6..c2da230b 100644 --- a/application/parser/remote/crawler_loader.py +++ b/application/parser/remote/crawler_loader.py @@ -2,16 +2,16 @@ import requests from urllib.parse import urlparse, urljoin from bs4 import BeautifulSoup from application.parser.remote.base import BaseRemote +from application.parser.schema.base import Document +from langchain_community.document_loaders import WebBaseLoader class CrawlerLoader(BaseRemote): def __init__(self, limit=10): - from langchain_community.document_loaders import WebBaseLoader self.loader = WebBaseLoader # Initialize the document loader self.limit = limit # Set the limit for the number of pages to scrape def load_data(self, inputs): url = inputs - # Check if the input is a list and if it is, use the first element if isinstance(url, list) and url: url = url[0] @@ -19,24 +19,29 @@ class CrawlerLoader(BaseRemote): if not urlparse(url).scheme: url = "http://" + url - visited_urls = set() # Keep track of URLs that have been visited - base_url = urlparse(url).scheme + "://" + urlparse(url).hostname # Extract the base URL - urls_to_visit = [url] # List of URLs to be visited, starting with the initial URL - loaded_content = [] # Store the loaded content from each URL + visited_urls = set() + base_url = urlparse(url).scheme + "://" + urlparse(url).hostname + urls_to_visit = [url] + loaded_content = [] - # Continue crawling until there are no more URLs to visit while urls_to_visit: - current_url = urls_to_visit.pop(0) # Get the next URL to visit - visited_urls.add(current_url) # Mark the URL as visited + current_url = urls_to_visit.pop(0) + visited_urls.add(current_url) - # Try to load and process the content from the current URL try: - response = requests.get(current_url) # Fetch the content of the current URL - response.raise_for_status() # Raise an exception for HTTP errors - loader = self.loader([current_url]) # Initialize the document loader for the current URL - loaded_content.extend(loader.load()) # Load the content and add it to the loaded_content list + response = requests.get(current_url) + response.raise_for_status() + loader = self.loader([current_url]) + docs = loader.load() + # Convert the loaded documents to your Document schema + for doc in docs: + loaded_content.append( + Document( + doc.page_content, + extra_info=doc.metadata + ) + ) except Exception as e: - # Print an error message if loading or processing fails and continue with the next URL print(f"Error processing URL {current_url}: {e}") continue @@ -45,15 +50,15 @@ class CrawlerLoader(BaseRemote): all_links = [ urljoin(current_url, a['href']) for a in soup.find_all('a', href=True) - if base_url in urljoin(current_url, a['href']) # Ensure links are from the same domain + if base_url in urljoin(current_url, a['href']) ] # Add new links to the list of URLs to visit if they haven't been visited yet urls_to_visit.extend([link for link in all_links if link not in visited_urls]) - urls_to_visit = list(set(urls_to_visit)) # Remove duplicate URLs + urls_to_visit = list(set(urls_to_visit)) # Stop crawling if the limit of pages to scrape is reached if self.limit is not None and len(visited_urls) >= self.limit: break - return loaded_content # Return the loaded content from all visited URLs + return loaded_content \ No newline at end of file diff --git a/application/parser/remote/crawler_markdown.py b/application/parser/remote/crawler_markdown.py new file mode 100644 index 00000000..3d199332 --- /dev/null +++ b/application/parser/remote/crawler_markdown.py @@ -0,0 +1,139 @@ +import requests +from urllib.parse import urlparse, urljoin +from bs4 import BeautifulSoup +from application.parser.remote.base import BaseRemote +import re +from markdownify import markdownify +from application.parser.schema.base import Document +import tldextract + +class CrawlerLoader(BaseRemote): + def __init__(self, limit=10, allow_subdomains=False): + """ + Given a URL crawl web pages up to `self.limit`, + convert HTML content to Markdown, and returning a list of Document objects. + + :param limit: The maximum number of pages to crawl. + :param allow_subdomains: If True, crawl pages on subdomains of the base domain. + """ + self.limit = limit + self.allow_subdomains = allow_subdomains + self.session = requests.Session() + + def load_data(self, inputs): + url = inputs + if isinstance(url, list) and url: + url = url[0] + + # Ensure the URL has a scheme (if not, default to http) + if not urlparse(url).scheme: + url = "http://" + url + + # Keep track of visited URLs to avoid revisiting the same page + visited_urls = set() + + # Determine the base domain for link filtering using tldextract + base_domain = self._get_base_domain(url) + urls_to_visit = {url} + documents = [] + + while urls_to_visit: + current_url = urls_to_visit.pop() + + # Skip if already visited + if current_url in visited_urls: + continue + visited_urls.add(current_url) + + # Fetch the page content + html_content = self._fetch_page(current_url) + if html_content is None: + continue + + # Convert the HTML to Markdown for cleaner text formatting + title, language, processed_markdown = self._process_html_to_markdown(html_content, current_url) + if processed_markdown: + # Create a Document for each visited page + documents.append( + Document( + processed_markdown, # content + None, # doc_id + None, # embedding + {"source": current_url, "title": title, "language": language} # extra_info + ) + ) + + # Extract links and filter them according to domain rules + new_links = self._extract_links(html_content, current_url) + filtered_links = self._filter_links(new_links, base_domain) + + # Add any new, not-yet-visited links to the queue + urls_to_visit.update(link for link in filtered_links if link not in visited_urls) + + # If we've reached the limit, stop crawling + if self.limit is not None and len(visited_urls) >= self.limit: + break + + return documents + + def _fetch_page(self, url): + try: + response = self.session.get(url, timeout=10) + response.raise_for_status() + return response.text + except requests.exceptions.RequestException as e: + print(f"Error fetching URL {url}: {e}") + return None + + def _process_html_to_markdown(self, html_content, current_url): + soup = BeautifulSoup(html_content, 'html.parser') + title_tag = soup.find('title') + title = title_tag.text.strip() if title_tag else "No Title" + + # Extract language + language_tag = soup.find('html') + language = language_tag.get('lang', 'en') if language_tag else "en" + + markdownified = markdownify(html_content, heading_style="ATX", newline_style="BACKSLASH") + # Reduce sequences of more than two newlines to exactly three + markdownified = re.sub(r'\n{3,}', '\n\n\n', markdownified) + return title, language, markdownified + + def _extract_links(self, html_content, current_url): + soup = BeautifulSoup(html_content, 'html.parser') + links = [] + for a in soup.find_all('a', href=True): + full_url = urljoin(current_url, a['href']) + links.append((full_url, a.text.strip())) + return links + + def _get_base_domain(self, url): + extracted = tldextract.extract(url) + # Reconstruct the domain as domain.suffix + base_domain = f"{extracted.domain}.{extracted.suffix}" + return base_domain + + def _filter_links(self, links, base_domain): + """ + Filter the extracted links to only include those that match the crawling criteria: + - If allow_subdomains is True, allow any link whose domain ends with the base_domain. + - If allow_subdomains is False, only allow exact matches of the base_domain. + """ + filtered = [] + for link, _ in links: + parsed_link = urlparse(link) + if not parsed_link.netloc: + continue + + extracted = tldextract.extract(parsed_link.netloc) + link_base = f"{extracted.domain}.{extracted.suffix}" + + if self.allow_subdomains: + # For subdomains: sub.example.com ends with example.com + if link_base == base_domain or link_base.endswith("." + base_domain): + filtered.append(link) + else: + # Exact domain match + if link_base == base_domain: + filtered.append(link) + return filtered \ No newline at end of file diff --git a/application/parser/remote/web_loader.py b/application/parser/remote/web_loader.py index a19e0c90..cc1cdcb8 100644 --- a/application/parser/remote/web_loader.py +++ b/application/parser/remote/web_loader.py @@ -1,5 +1,7 @@ from application.parser.remote.base import BaseRemote +from application.parser.schema.base import Document from langchain_community.document_loaders import WebBaseLoader +from urllib.parse import urlparse headers = { "User-Agent": "Mozilla/5.0", @@ -23,10 +25,20 @@ class WebLoader(BaseRemote): urls = [urls] documents = [] for url in urls: + # Check if the URL scheme is provided, if not, assume http + if not urlparse(url).scheme: + url = "http://" + url try: loader = self.loader([url], header_template=headers) - documents.extend(loader.load()) + loaded_docs = loader.load() + for doc in loaded_docs: + documents.append( + Document( + doc.page_content, + extra_info=doc.metadata, + ) + ) except Exception as e: print(f"Error processing URL {url}: {e}") continue - return documents + return documents \ No newline at end of file diff --git a/application/requirements.txt b/application/requirements.txt index 2cc7b1a3..c193f38d 100644 --- a/application/requirements.txt +++ b/application/requirements.txt @@ -1,27 +1,27 @@ anthropic==0.40.0 -boto3==1.34.153 +boto3==1.35.97 beautifulsoup4==4.12.3 -celery==5.3.6 +celery==5.4.0 dataclasses-json==0.6.7 docx2txt==0.8 duckduckgo-search==6.3.0 ebooklib==0.18 -elastic-transport==8.15.1 +elastic-transport==8.17.0 elasticsearch==8.17.0 escodegen==1.0.11 esprima==4.0.1 esutils==1.0.1 -Flask==3.0.3 +Flask==3.1.0 faiss-cpu==1.9.0.post1 flask-restx==1.3.0 google-genai==0.5.0 google-generativeai==0.8.3 -gTTS==2.3.2 +gTTS==2.5.4 gunicorn==23.0.0 html2text==2024.2.26 javalang==0.13.0 jinja2==3.1.5 -jiter==0.5.0 +jiter==0.8.2 jmespath==1.0.1 joblib==1.4.2 jsonpatch==1.33 @@ -30,62 +30,64 @@ jsonschema==4.23.0 jsonschema-spec==0.2.4 jsonschema-specifications==2023.7.1 kombu==5.4.2 -langchain==0.3.13 -langchain-community==0.3.13 -langchain-core==0.3.28 -langchain-openai==0.2.14 -langchain-text-splitters==0.3.4 -langsmith==0.2.3 +langchain==0.3.14 +langchain-community==0.3.14 +langchain-core==0.3.29 +langchain-openai==0.3.0 +langchain-text-splitters==0.3.5 +langsmith==0.2.10 lazy-object-proxy==1.10.0 lxml==5.3.0 -markupsafe==2.1.5 -marshmallow==3.22.0 +markupsafe==3.0.2 +marshmallow==3.24.1 mpmath==1.3.0 multidict==6.1.0 mypy-extensions==1.0.0 -networkx==3.3 +networkx==3.4.2 numpy==2.2.1 -openai==1.58.1 +openai==1.59.5 openapi-schema-validator==0.6.2 openapi-spec-validator==0.6.0 -openapi3-parser==1.1.18 -orjson==3.10.7 +openapi3-parser==1.1.19 +orjson==3.10.14 packaging==24.1 pandas==2.2.3 openpyxl==3.1.5 -pathable==0.4.3 -pillow==10.4.0 +pathable==0.4.4 +pillow==11.1.0 portalocker==2.10.1 prance==23.6.21.0 -primp==0.6.3 -prompt-toolkit==3.0.47 -protobuf==5.28.2 +primp==0.10.0 +prompt-toolkit==3.0.48 +protobuf==5.29.3 py==1.11.0 -pydantic==2.9.2 -pydantic-core==2.23.4 -pydantic-settings==2.4.0 -pymongo==4.8.0 +pydantic==2.10.4 +pydantic-core==2.27.2 +pydantic-settings==2.7.1 +pymongo==4.10.1 pypdf2==3.0.1 python-dateutil==2.9.0.post0 python-dotenv==1.0.1 python-pptx==1.0.2 -qdrant-client==1.11.0 -redis==5.0.1 +qdrant-client==1.12.2 +redis==5.2.1 referencing==0.30.2 -regex==2024.9.11 +regex==2024.11.6 requests==2.32.3 retry==0.9.2 sentence-transformers==3.3.1 -tiktoken==0.7.0 +tiktoken==0.8.0 tokenizers==0.21.0 -torch==2.4.1 -tqdm==4.66.5 -transformers==4.47.0 +torch==2.5.1 +tqdm==4.67.1 +transformers==4.48.0 typing-extensions==4.12.2 typing-inspect==0.9.0 tzdata==2024.2 -urllib3==2.2.3 +urllib3==2.3.0 vine==5.1.0 wcwidth==0.2.13 werkzeug==3.1.3 -yarl==1.18.3 \ No newline at end of file +yarl==1.18.3 +markdownify==0.14.1 +tldextract==5.1.3 \ No newline at end of file diff --git a/application/worker.py b/application/worker.py index f4f181e5..df0bbe7d 100755 --- a/application/worker.py +++ b/application/worker.py @@ -203,53 +203,61 @@ def remote_worker( sync_frequency="never", operation_mode="upload", doc_id=None, -): +): full_path = os.path.join(directory, user, name_job) - if not os.path.exists(full_path): os.makedirs(full_path) + self.update_state(state="PROGRESS", meta={"current": 1}) - logging.info( - f"Remote job: {full_path}", - extra={"user": user, "job": name_job, "source_data": source_data}, - ) + try: + logging.info("Initializing remote loader with type: %s", loader) + remote_loader = RemoteCreator.create_loader(loader) + raw_docs = remote_loader.load_data(source_data) - remote_loader = RemoteCreator.create_loader(loader) - raw_docs = remote_loader.load_data(source_data) + chunker = Chunker( + chunking_strategy="classic_chunk", + max_tokens=MAX_TOKENS, + min_tokens=MIN_TOKENS, + duplicate_headers=False + ) + docs = chunker.chunk(documents=raw_docs) + docs = [Document.to_langchain_format(raw_doc) for raw_doc in raw_docs] + tokens = count_tokens_docs(docs) + logging.info("Total tokens calculated: %d", tokens) - chunker = Chunker( - chunking_strategy="classic_chunk", - max_tokens=MAX_TOKENS, - min_tokens=MIN_TOKENS, - duplicate_headers=False - ) - docs = chunker.chunk(documents=raw_docs) + if operation_mode == "upload": + id = ObjectId() + embed_and_store_documents(docs, full_path, id, self) + elif operation_mode == "sync": + if not doc_id or not ObjectId.is_valid(doc_id): + logging.error("Invalid doc_id provided for sync operation: %s", doc_id) + raise ValueError("doc_id must be provided for sync operation.") + id = ObjectId(doc_id) + embed_and_store_documents(docs, full_path, id, self) - tokens = count_tokens_docs(docs) - if operation_mode == "upload": - id = ObjectId() - embed_and_store_documents(docs, full_path, id, self) - elif operation_mode == "sync": - if not doc_id or not ObjectId.is_valid(doc_id): - raise ValueError("doc_id must be provided for sync operation.") - id = ObjectId(doc_id) - embed_and_store_documents(docs, full_path, id, self) - self.update_state(state="PROGRESS", meta={"current": 100}) + self.update_state(state="PROGRESS", meta={"current": 100}) - file_data = { - "name": name_job, - "user": user, - "tokens": tokens, - "retriever": retriever, - "id": str(id), - "type": loader, - "remote_data": source_data, - "sync_frequency": sync_frequency, - } - upload_index(full_path, file_data) + file_data = { + "name": name_job, + "user": user, + "tokens": tokens, + "retriever": retriever, + "id": str(id), + "type": loader, + "remote_data": source_data, + "sync_frequency": sync_frequency, + } + upload_index(full_path, file_data) - shutil.rmtree(full_path) + except Exception as e: + logging.error("Error in remote_worker task: %s", str(e), exc_info=True) + raise + finally: + if os.path.exists(full_path): + shutil.rmtree(full_path) + + logging.info("remote_worker task completed successfully") return {"urls": source_data, "name_job": name_job, "user": user, "limited": False} def sync( diff --git a/docs/package-lock.json b/docs/package-lock.json index 2e9c7f7d..e4ffb04f 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -7,8 +7,8 @@ "license": "MIT", "dependencies": { "@vercel/analytics": "^1.1.1", - "docsgpt-react": "^0.4.8", - "next": "^14.2.20", + "docsgpt-react": "^0.4.9", + "next": "^14.2.22", "nextra": "^2.13.2", "nextra-theme-docs": "^2.13.2", "react": "^18.2.0", @@ -931,14 +931,14 @@ } }, "node_modules/@next/env": { - "version": "14.2.20", - "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.20.tgz", - "integrity": "sha512-JfDpuOCB0UBKlEgEy/H6qcBSzHimn/YWjUHzKl1jMeUO+QVRdzmTTl8gFJaNO87c8DXmVKhFCtwxQ9acqB3+Pw==" + "version": "14.2.22", + "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.22.tgz", + "integrity": "sha512-EQ6y1QeNQglNmNIXvwP/Bb+lf7n9WtgcWvtoFsHquVLCJUuxRs+6SfZ5EK0/EqkkLex4RrDySvKgKNN7PXip7Q==" }, "node_modules/@next/swc-darwin-arm64": { - "version": "14.2.20", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.20.tgz", - "integrity": "sha512-WDfq7bmROa5cIlk6ZNonNdVhKmbCv38XteVFYsxea1vDJt3SnYGgxLGMTXQNfs5OkFvAhmfKKrwe7Y0Hs+rWOg==", + "version": "14.2.22", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.22.tgz", + "integrity": "sha512-HUaLiehovgnqY4TMBZJ3pDaOsTE1spIXeR10pWgdQVPYqDGQmHJBj3h3V6yC0uuo/RoY2GC0YBFRkOX3dI9WVQ==", "cpu": [ "arm64" ], @@ -951,9 +951,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "14.2.20", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.20.tgz", - "integrity": "sha512-XIQlC+NAmJPfa2hruLvr1H1QJJeqOTDV+v7tl/jIdoFvqhoihvSNykLU/G6NMgoeo+e/H7p/VeWSOvMUHKtTIg==", + "version": "14.2.22", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.22.tgz", + "integrity": "sha512-ApVDANousaAGrosWvxoGdLT0uvLBUC+srqOcpXuyfglA40cP2LBFaGmBjhgpxYk5z4xmunzqQvcIgXawTzo2uQ==", "cpu": [ "x64" ], @@ -966,9 +966,9 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "14.2.20", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.20.tgz", - "integrity": "sha512-pnzBrHTPXIMm5QX3QC8XeMkpVuoAYOmyfsO4VlPn+0NrHraNuWjdhe+3xLq01xR++iCvX+uoeZmJDKcOxI201Q==", + "version": "14.2.22", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.22.tgz", + "integrity": "sha512-3O2J99Bk9aM+d4CGn9eEayJXHuH9QLx0BctvWyuUGtJ3/mH6lkfAPRI4FidmHMBQBB4UcvLMfNf8vF0NZT7iKw==", "cpu": [ "arm64" ], @@ -981,9 +981,9 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "14.2.20", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.20.tgz", - "integrity": "sha512-WhJJAFpi6yqmUx1momewSdcm/iRXFQS0HU2qlUGlGE/+98eu7JWLD5AAaP/tkK1mudS/rH2f9E3WCEF2iYDydQ==", + "version": "14.2.22", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.22.tgz", + "integrity": "sha512-H/hqfRz75yy60y5Eg7DxYfbmHMjv60Dsa6IWHzpJSz4MRkZNy5eDnEW9wyts9bkxwbOVZNPHeb3NkqanP+nGPg==", "cpu": [ "arm64" ], @@ -996,9 +996,9 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "14.2.20", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.20.tgz", - "integrity": "sha512-ao5HCbw9+iG1Kxm8XsGa3X174Ahn17mSYBQlY6VGsdsYDAbz/ZP13wSLfvlYoIDn1Ger6uYA+yt/3Y9KTIupRg==", + "version": "14.2.22", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.22.tgz", + "integrity": "sha512-LckLwlCLcGR1hlI5eiJymR8zSHPsuruuwaZ3H2uudr25+Dpzo6cRFjp/3OR5UYJt8LSwlXv9mmY4oI2QynwpqQ==", "cpu": [ "x64" ], @@ -1011,9 +1011,9 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "14.2.20", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.20.tgz", - "integrity": "sha512-CXm/kpnltKTT7945np6Td3w7shj/92TMRPyI/VvveFe8+YE+/YOJ5hyAWK5rpx711XO1jBCgXl211TWaxOtkaA==", + "version": "14.2.22", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.22.tgz", + "integrity": "sha512-qGUutzmh0PoFU0fCSu0XYpOfT7ydBZgDfcETIeft46abPqP+dmePhwRGLhFKwZWxNWQCPprH26TjaTxM0Nv8mw==", "cpu": [ "x64" ], @@ -1026,9 +1026,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "14.2.20", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.20.tgz", - "integrity": "sha512-upJn2HGQgKNDbXVfIgmqT2BN8f3z/mX8ddoyi1I565FHbfowVK5pnMEwauvLvaJf4iijvuKq3kw/b6E9oIVRWA==", + "version": "14.2.22", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.22.tgz", + "integrity": "sha512-K6MwucMWmIvMb9GlvT0haYsfIPxfQD8yXqxwFy4uLFMeXIb2TcVYQimxkaFZv86I7sn1NOZnpOaVk5eaxThGIw==", "cpu": [ "arm64" ], @@ -1041,9 +1041,9 @@ } }, "node_modules/@next/swc-win32-ia32-msvc": { - "version": "14.2.20", - "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.20.tgz", - "integrity": "sha512-igQW/JWciTGJwj3G1ipalD2V20Xfx3ywQy17IV0ciOUBbFhNfyU1DILWsTi32c8KmqgIDviUEulW/yPb2FF90w==", + "version": "14.2.22", + "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.22.tgz", + "integrity": "sha512-5IhDDTPEbzPR31ZzqHe90LnNe7BlJUZvC4sA1thPJV6oN5WmtWjZ0bOYfNsyZx00FJt7gggNs6SrsX0UEIcIpA==", "cpu": [ "ia32" ], @@ -1056,9 +1056,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "14.2.20", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.20.tgz", - "integrity": "sha512-AFmqeLW6LtxeFTuoB+MXFeM5fm5052i3MU6xD0WzJDOwku6SkZaxb1bxjBaRC8uNqTRTSPl0yMFtjNowIVI67w==", + "version": "14.2.22", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.22.tgz", + "integrity": "sha512-nvRaB1PyG4scn9/qNzlkwEwLzuoPH3Gjp7Q/pLuwUgOTt1oPMlnCI3A3rgkt+eZnU71emOiEv/mR201HoURPGg==", "cpu": [ "x64" ], @@ -4057,10 +4057,9 @@ } }, "node_modules/docsgpt-react": { - "version": "0.4.8", - "resolved": "https://registry.npmjs.org/docsgpt-react/-/docsgpt-react-0.4.8.tgz", - "integrity": "sha512-A4+wZVDDtX6J84SHBl2VZpDAydy1kwKUOeGcIiq0uY+JmP+ZKst879vsfgEN1WY3ZNo8F+AC1w+3g/jOZ3Ma8g==", - "license": "Apache-2.0", + "version": "0.4.9", + "resolved": "https://registry.npmjs.org/docsgpt-react/-/docsgpt-react-0.4.9.tgz", + "integrity": "sha512-mGGbd4IGVHrQVVdgoej991Vpl/hYkTuKz5Ax95hvqSbWDZELZnEx2/AZajAII5AayUZKWYaEFRluewUiGJVSbA==", "dependencies": { "@babel/plugin-transform-flow-strip-types": "^7.23.3", "@parcel/resolver-glob": "^2.12.0", @@ -5186,9 +5185,9 @@ "integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==" }, "node_modules/katex": { - "version": "0.16.10", - "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.10.tgz", - "integrity": "sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==", + "version": "0.16.21", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.21.tgz", + "integrity": "sha512-XvqR7FgOHtWupfMiigNzmh+MgUVmDGU2kXZm899ZkPfcuoPuFxyHmXsgATDpFZDAXCI8tvinaVcDo8PIIJSo4A==", "funding": [ "https://opencollective.com/katex", "https://github.com/sponsors/katex" @@ -6759,11 +6758,11 @@ } }, "node_modules/next": { - "version": "14.2.20", - "resolved": "https://registry.npmjs.org/next/-/next-14.2.20.tgz", - "integrity": "sha512-yPvIiWsiyVYqJlSQxwmzMIReXn5HxFNq4+tlVQ812N1FbvhmE+fDpIAD7bcS2mGYQwPJ5vAsQouyme2eKsxaug==", + "version": "14.2.22", + "resolved": "https://registry.npmjs.org/next/-/next-14.2.22.tgz", + "integrity": "sha512-Ps2caobQ9hlEhscLPiPm3J3SYhfwfpMqzsoCMZGWxt9jBRK9hoBZj2A37i8joKhsyth2EuVKDVJCTF5/H4iEDw==", "dependencies": { - "@next/env": "14.2.20", + "@next/env": "14.2.22", "@swc/helpers": "0.5.5", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", @@ -6778,15 +6777,15 @@ "node": ">=18.17.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "14.2.20", - "@next/swc-darwin-x64": "14.2.20", - "@next/swc-linux-arm64-gnu": "14.2.20", - "@next/swc-linux-arm64-musl": "14.2.20", - "@next/swc-linux-x64-gnu": "14.2.20", - "@next/swc-linux-x64-musl": "14.2.20", - "@next/swc-win32-arm64-msvc": "14.2.20", - "@next/swc-win32-ia32-msvc": "14.2.20", - "@next/swc-win32-x64-msvc": "14.2.20" + "@next/swc-darwin-arm64": "14.2.22", + "@next/swc-darwin-x64": "14.2.22", + "@next/swc-linux-arm64-gnu": "14.2.22", + "@next/swc-linux-arm64-musl": "14.2.22", + "@next/swc-linux-x64-gnu": "14.2.22", + "@next/swc-linux-x64-musl": "14.2.22", + "@next/swc-win32-arm64-msvc": "14.2.22", + "@next/swc-win32-ia32-msvc": "14.2.22", + "@next/swc-win32-x64-msvc": "14.2.22" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", diff --git a/docs/package.json b/docs/package.json index b9f53250..8e6ad1d3 100644 --- a/docs/package.json +++ b/docs/package.json @@ -7,8 +7,8 @@ "license": "MIT", "dependencies": { "@vercel/analytics": "^1.1.1", - "docsgpt-react": "^0.4.8", - "next": "^14.2.20", + "docsgpt-react": "^0.4.9", + "next": "^14.2.22", "nextra": "^2.13.2", "nextra-theme-docs": "^2.13.2", "react": "^18.2.0", diff --git a/extensions/react-widget/package-lock.json b/extensions/react-widget/package-lock.json index 02ef326b..bae94d08 100644 --- a/extensions/react-widget/package-lock.json +++ b/extensions/react-widget/package-lock.json @@ -1,12 +1,12 @@ { "name": "docsgpt", - "version": "0.4.8", + "version": "0.4.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "docsgpt", - "version": "0.4.8", + "version": "0.4.9", "license": "Apache-2.0", "dependencies": { "@babel/plugin-transform-flow-strip-types": "^7.23.3", diff --git a/extensions/react-widget/package.json b/extensions/react-widget/package.json index c6401d30..3e4f15da 100644 --- a/extensions/react-widget/package.json +++ b/extensions/react-widget/package.json @@ -1,6 +1,6 @@ { - "name": "docsgpt-react", - "version": "0.4.8", + "name": "docsgpt", + "version": "0.4.9", "private": false, "description": "DocsGPT 🦖 is an innovative open-source tool designed to simplify the retrieval of information from project documentation using advanced GPT models 🤖.", "source": "./src/index.html", diff --git a/extensions/react-widget/publish.sh b/extensions/react-widget/publish.sh index c4545d85..129c4bcf 100755 --- a/extensions/react-widget/publish.sh +++ b/extensions/react-widget/publish.sh @@ -1,43 +1,85 @@ #!/bin/bash -## chmod +x publish.sh - to upgrade ownership set -e -cat package.json >> package_copy.json -cat package-lock.json >> package-lock_copy.json + +# Create backup of original files +cp package.json package_original.json +cp package-lock.json package-lock_original.json + +# Store the latest version after publishing +LATEST_VERSION="" + publish_package() { - PACKAGE_NAME=$1 - BUILD_COMMAND=$2 - # Update package name in package.json - jq --arg name "$PACKAGE_NAME" '.name=$name' package.json > temp.json && mv temp.json package.json + PACKAGE_NAME=$1 + BUILD_COMMAND=$2 + IS_REACT=$3 - # Remove 'target' key if the package name is 'docsgpt-react' - if [ "$PACKAGE_NAME" = "docsgpt-react" ]; then - jq 'del(.targets)' package.json > temp.json && mv temp.json package.json - fi + echo "Preparing to publish ${PACKAGE_NAME}..." + + # Restore original package.json state before each publish + cp package_original.json package.json + cp package-lock_original.json package-lock.json - if [ -d "dist" ]; then - echo "Deleting existing dist directory..." - rm -rf dist - fi + # Update package name in package.json + jq --arg name "$PACKAGE_NAME" '.name=$name' package.json > temp.json && mv temp.json package.json - npm version patch + # Handle targets based on package type + if [ "$IS_REACT" = "true" ]; then + echo "Removing targets for React library build..." + jq 'del(.targets)' package.json > temp.json && mv temp.json package.json + fi - npm run "$BUILD_COMMAND" + # Clean dist directory + if [ -d "dist" ]; then + echo "Cleaning dist directory..." + rm -rf dist + fi - # Publish to npm - npm publish - # Clean up - mv package_copy.json package.json - mv package-lock_copy.json package-lock.json - echo "Published ${PACKAGE_NAME}" + # update version and store it + LATEST_VERSION=$(npm version patch) + echo "New version: ${LATEST_VERSION}" + + # Build package + npm run "$BUILD_COMMAND" + + # Replace npm publish with npm pack for testing + npm publish + + echo "Successfully packaged ${PACKAGE_NAME}" + + # Log the bundle size + TARBALL="${PACKAGE_NAME}-${LATEST_VERSION#v}.tgz" + if [ -f "$TARBALL" ]; then + BUNDLE_SIZE=$(du -h "$TARBALL" | cut -f1) + echo "Bundle size for ${PACKAGE_NAME}: ${BUNDLE_SIZE}" + else + echo "Error: ${TARBALL} not found." + exit 1 + fi } -# Publish docsgpt package -publish_package "docsgpt" "build" +# First publish docsgpt (HTML bundle) +publish_package "docsgpt" "build" "false" -# Publish docsgpt-react package -publish_package "docsgpt-react" "build:react" +# Then publish docsgpt-react (React library) +publish_package "docsgpt-react" "build:react" "true" +# Restore original state but keep the updated version +cp package_original.json package.json +cp package-lock_original.json package-lock.json -rm -rf package_copy.json -rm -rf package-lock_copy.json -echo "---Process completed---" \ No newline at end of file +# Update the version in the final package.json +jq --arg version "${LATEST_VERSION#v}" '.version=$version' package.json > temp.json && mv temp.json package.json + +# Run npm install to update package-lock.json with the new version +npm install --package-lock-only + +# Cleanup backup files +rm -f package_original.json +rm -f package-lock_original.json +rm -f temp.json + +echo "---Process completed---" +echo "Final version in package.json: $(jq -r '.version' package.json)" +echo "Final version in package-lock.json: $(jq -r '.version' package-lock.json)" +echo "Generated test packages:" +ls *.tgz diff --git a/extensions/react-widget/src/components/SearchBar.tsx b/extensions/react-widget/src/components/SearchBar.tsx index 42262e08..c647991f 100644 --- a/extensions/react-widget/src/components/SearchBar.tsx +++ b/extensions/react-widget/src/components/SearchBar.tsx @@ -1,15 +1,24 @@ -import React from 'react' -import styled, { ThemeProvider } from 'styled-components'; +import React from 'react'; +import styled, { ThemeProvider, createGlobalStyle } from 'styled-components'; import { WidgetCore } from './DocsGPTWidget'; import { SearchBarProps } from '@/types'; -import { getSearchResults } from '../requests/searchAPI' +import { getSearchResults } from '../requests/searchAPI'; import { Result } from '@/types'; import MarkdownIt from 'markdown-it'; -import { getOS, preprocessSearchResultsToHTML } from '../utils/helper' +import { getOS, processMarkdownString } from '../utils/helper'; +import DOMPurify from 'dompurify'; +import { + CodeIcon, + TextAlignLeftIcon, + HeadingIcon, + ReaderIcon, + ListBulletIcon, + QuoteIcon +} from '@radix-ui/react-icons'; const themes = { dark: { - bg: '#000', - text: '#fff', + bg: '#202124', + text: '#EDEDED', primary: { text: "#FAFAFA", bg: '#111111' @@ -20,8 +29,8 @@ const themes = { } }, light: { - bg: '#fff', - text: '#000', + bg: '#EAEAEA', + text: '#171717', primary: { text: "#222327", bg: "#fff" @@ -33,16 +42,30 @@ const themes = { } } +const GlobalStyle = createGlobalStyle` + .highlight { + color:#007EE6; + } +`; + +const loadGeistFont = () => { + const link = document.createElement('link'); + link.href = 'https://fonts.googleapis.com/css2?family=Geist:wght@100..900&display=swap'; + link.rel = 'stylesheet'; + document.head.appendChild(link); +}; + const Main = styled.div` - all:initial; - font-family: sans-serif; + all: initial; + font-family: 'Geist', sans-serif; ` -const TextField = styled.input<{ inputWidth: string }>` - padding: 6px 6px; +const SearchButton = styled.button<{ inputWidth: string }>` + padding: 6px 6px; + font-family: inherit; width: ${({ inputWidth }) => inputWidth}; border-radius: 8px; display: inline; - color: ${props => props.theme.primary.text}; + color: ${props => props.theme.secondary.text}; outline: none; border: none; background-color: ${props => props.theme.secondary.bg}; @@ -50,14 +73,8 @@ const TextField = styled.input<{ inputWidth: string }>` -moz-appearance: none; appearance: none; transition: background-color 128ms linear; - &:focus { - outline: none; - box-shadow: - 0px 0px 0px 2px rgba(0, 109, 199), - 0px 0px 6px rgb(0, 90, 163), - 0px 2px 6px rgba(0, 0, 0, 0.1) ; - background-color: ${props => props.theme.primary.bg}; - } + text-align: left; + cursor: pointer; ` const Container = styled.div` @@ -65,61 +82,120 @@ const Container = styled.div` display: inline-block; ` const SearchResults = styled.div` - position: absolute; - display: block; + position: fixed; + display: flex; + flex-direction: column; background-color: ${props => props.theme.primary.bg}; - border: 1px solid rgba(0, 0, 0, .1); - border-radius: 12px; - padding: 8px; - width: 576px; - min-width: 96%; + border: 1px solid ${props => props.theme.bg}; + border-radius: 15px; + padding: 8px 0px 8px 0px; + width: 792px; + max-width: 90vw; + height: 396px; z-index: 100; - height: 25vh; - overflow-y: auto; - top: 32px; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); color: ${props => props.theme.primary.text}; - scrollbar-color: lab(48.438 0 0 / 0.4) rgba(0, 0, 0, 0); + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(16px); + box-sizing: border-box; + + @media only screen and (max-width: 768px) { + height: 80vh; + width: 90vw; + } +`; + +const SearchResultsScroll = styled.div` + flex: 1; + overflow-y: auto; + overflow-x: hidden; scrollbar-gutter: stable; scrollbar-width: thin; - box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.1); - backdrop-filter: blur(16px); - @media only screen and (max-width: 768px) { - max-height: 100vh; - max-width: 80vw; - overflow: auto; + scrollbar-color: #383838 transparent; + padding: 0 16px; +`; + +const IconTitleWrapper = styled.div` + display: flex; + align-items: center; + gap: 8px; + + .element-icon{ + margin: 4px; } -` +`; + const Title = styled.h3` - font-size: 14px; + font-size: 15px; + font-weight: 400; color: ${props => props.theme.primary.text}; - opacity: 0.8; - padding-bottom: 6px; - font-weight: 600; - text-transform: uppercase; - border-bottom: 1px solid ${(props) => props.theme.secondary.text}; -` + margin: 0; + overflow-wrap: break-word; + white-space: normal; + overflow: hidden; + text-overflow: ellipsis; +`; +const ContentWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 12px; +`; const Content = styled.div` - font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; + display: flex; + margin-left: 8px; + flex-direction: column; + gap: 8px; + padding: 4px 0px 0px 12px; + font-size: 15px; + color: ${props => props.theme.primary.text}; + line-height: 1.6; + border-left: 2px solid #585858; + overflow: hidden; ` +const ContentSegment = styled.div` + display: flex; + align-items: flex-start; + gap: 8px; + padding-right: 16px; + overflow-wrap: break-word; + white-space: normal; + overflow: hidden; + text-overflow: ellipsis; +` + const ResultWrapper = styled.div` - padding: 4px 8px 4px 8px; - border-radius: 8px; + display: flex; + align-items: flex-start; + width: 100%; + box-sizing: border-box; + padding: 8px 16px; cursor: pointer; - &.contains-source:hover{ - background-color: rgba(0, 92, 197, 0.15); - ${Title} { - color: rgb(0, 126, 230); - } + background-color: ${props => props.theme.primary.bg}; + font-family: 'Geist', sans-serif; + transition: background-color 0.2s; + border-radius: 8px; + + word-wrap: break-word; + overflow-wrap: break-word; + word-break: break-word; + white-space: normal; + overflow: hidden; + text-overflow: ellipsis; + + &:hover { + background-color: ${props => props.theme.bg}; } ` const Markdown = styled.div` -line-height:20px; -font-size: 12px; +line-height:18px; +font-size: 11px; white-space: pre-wrap; pre { padding: 8px; width: 90%; - font-size: 12px; + font-size: 11px; border-radius: 6px; overflow-x: auto; background-color: #1B1C1F; @@ -127,7 +203,7 @@ white-space: pre-wrap; } h1,h2 { - font-size: 16px; + font-size: 14px; font-weight: 600; color: ${(props) => props.theme.text}; opacity: 0.8; @@ -135,20 +211,20 @@ white-space: pre-wrap; h3 { - font-size: 14px; + font-size: 12px; } p { margin: 0px; line-height: 1.35rem; - font-size: 12px; + font-size: 11px; } code:not(pre code) { border-radius: 6px; padding: 2px 2px; margin: 2px; - font-size: 10px; + font-size: 9px; display: inline; background-color: #646464; color: #fff ; @@ -166,14 +242,20 @@ white-space: pre-wrap; const Toolkit = styled.kbd` position: absolute; right: 4px; - top: 4px; + top: 50%; + transform: translateY(-50%); background-color: ${(props) => props.theme.primary.bg}; color: ${(props) => props.theme.secondary.text}; font-weight: 600; font-size: 10px; - padding: 3px; + padding: 3px 6px; border: 1px solid ${(props) => props.theme.secondary.text}; border-radius: 4px; + display: flex; + align-items: center; + justify-content: center; + z-index: 1; + pointer-events: none; ` const Loader = styled.div` margin: 2rem auto; @@ -197,76 +279,130 @@ const Loader = styled.div` const NoResults = styled.div` margin-top: 2rem; text-align: center; - font-size: 1rem; + font-size: 14px; color: #888; `; -const InfoButton = styled.button` - cursor: pointer; - padding: 10px 4px 10px 4px; - display: block; - width: 100%; - color: inherit; +const AskAIButton = styled.button` + display: flex; + align-items: center; + justify-content: flex-start; + gap: 12px; + width: calc(100% - 32px); + margin: 0 16px 16px 16px; + box-sizing: border-box; + height: 50px; + padding: 8px 24px; + border: none; border-radius: 6px; - background-color: ${(props) => props.theme.bg}; - text-align: center; - font-size: 14px; - margin-bottom: 8px; - border:1px solid ${(props) => props.theme.secondary.text}; + background-color: ${props => props.theme.bg}; + color: ${props => props.theme.text}; + cursor: pointer; + transition: background-color 0.2s, box-shadow 0.2s; + font-size: 16px; + + &:hover { + opacity: 0.8; + } +` +const SearchHeader = styled.div` + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 12px; + padding-bottom: 12px; + border-bottom: 1px solid ${props => props.theme.bg}; +` + +const TextField = styled.input` + width: calc(100% - 32px); + margin: 0 16px; + padding: 12px 16px; + border: none; + background-color: transparent; + color: ${props => props.theme.text}; + font-size: 20px; + font-weight: 400; + outline: none; + &:focus { + border-color: none; + } +` + +const EscapeInstruction = styled.kbd` + display: flex; + align-items: center; + justify-content: center; + margin: 12px 16px 0; + padding: 4px 8px; + border-radius: 4px; + background-color: transparent; + border: 1px solid ${props => props.theme.secondary.text}; + color: ${props => props.theme.text}; + font-size: 12px; + font-family: 'Geist', sans-serif; + white-space: nowrap; + cursor: pointer; + width: fit-content; + &:hover { + background-color: rgba(255, 255, 255, 0.1); + } ` export const SearchBar = ({ apiKey = "74039c6d-bff7-44ce-ae55-2973cbf13837", apiHost = "https://gptcloud.arc53.com", theme = "dark", placeholder = "Search or Ask AI...", - width = "256px" + width = "256px", + buttonText = "Search here" }: SearchBarProps) => { const [input, setInput] = React.useState(""); const [loading, setLoading] = React.useState(false); const [isWidgetOpen, setIsWidgetOpen] = React.useState(false); const inputRef = React.useRef(null); const containerRef = React.useRef(null); - const [isResultVisible, setIsResultVisible] = React.useState(true); + const [isResultVisible, setIsResultVisible] = React.useState(false); const [results, setResults] = React.useState([]); const debounceTimeout = React.useRef | null>(null); - const abortControllerRef = React.useRef(null) + const abortControllerRef = React.useRef(null); const browserOS = getOS(); - function isTouchDevice() { - return 'ontouchstart' in window; - } - const isTouch = isTouchDevice(); + const isTouch = 'ontouchstart' in window; + const getKeyboardInstruction = () => { - if (isResultVisible) return "Enter" - if (browserOS === 'mac') - return "⌘ K" - else - return "Ctrl K" - } + if (isResultVisible) return "Enter"; + return browserOS === 'mac' ? '⌘ + K' : 'Ctrl + K'; + }; + React.useEffect(() => { - const handleFocusSearch = (event: KeyboardEvent) => { + loadGeistFont() + const handleClickOutside = (event: MouseEvent) => { + if (containerRef.current && !containerRef.current.contains(event.target as Node)) { + setIsResultVisible(false); + } + }; + + const handleKeyDown = (event: KeyboardEvent) => { if ( ((browserOS === 'win' || browserOS === 'linux') && event.ctrlKey && event.key === 'k') || (browserOS === 'mac' && event.metaKey && event.key === 'k') ) { event.preventDefault(); inputRef.current?.focus(); - } - } - const handleClickOutside = (event: MouseEvent) => { - if ( - containerRef.current && - !containerRef.current.contains(event.target as Node) - ) { + setIsResultVisible(true); + } else if (event.key === 'Escape') { setIsResultVisible(false); } }; + + document.addEventListener('mousedown', handleClickOutside); - document.addEventListener('keydown', handleFocusSearch); + document.addEventListener('keydown', handleKeyDown); return () => { - setIsResultVisible(true); document.removeEventListener('mousedown', handleClickOutside); + document.removeEventListener('keydown', handleKeyDown); }; - }, []) + }, []); + React.useEffect(() => { if (!input) { setResults([]); @@ -291,8 +427,6 @@ export const SearchBar = ({ }, 500); return () => { - console.log(results); - abortController.abort(); clearTimeout(debounceTimeout.current ?? undefined); }; @@ -304,73 +438,106 @@ export const SearchBar = ({ openWidget(); } }; + const openWidget = () => { setIsWidgetOpen(true); - setIsResultVisible(false) - } + setIsResultVisible(false); + }; + const handleClose = () => { setIsWidgetOpen(false); - } - const md = new MarkdownIt(); + setIsResultVisible(true); + }; + return (
+ - setIsResultVisible(true)} inputWidth={width} - onFocus={() => setIsResultVisible(true)} - ref={inputRef} - onSubmit={() => setIsWidgetOpen(true)} - onKeyDown={(e) => handleKeyDown(e)} - placeholder={placeholder} - value={input} - onChange={(e) => setInput(e.target.value)} - /> + > + {buttonText} + { - input.length > 0 && isResultVisible && ( + isResultVisible && ( - - { - isTouch ? - "Ask the AI" : - <> - Press Enter to ask the AI - - } - - {!loading ? - (results.length > 0 ? - results.map((res, key) => { - const containsSource = res.source !== 'local'; - const filteredResults = preprocessSearchResultsToHTML(res.text,input) - if (filteredResults) - return ( - { - if (!containsSource) return; - window.open(res.source, '_blank', 'noopener, noreferrer') - }} - className={containsSource ? "contains-source" : ""}> - {res.title} - - - - - ) - else { - setResults((prevItems) => prevItems.filter((_, index) => index !== key)); - } - }) - : - No results - ) - : - - } + + setInput(e.target.value)} + onKeyDown={(e) => handleKeyDown(e)} + placeholder={placeholder} + autoFocus + /> + setIsResultVisible(false)}> + Esc + + + + DocsGPT + Ask the AI + + + {!loading ? ( + results.length > 0 ? ( + results.map((res, key) => { + const containsSource = res.source !== 'local'; + const processedResults = processMarkdownString(res.text, input); + if (processedResults) + return ( + { + if (!containsSource) return; + window.open(res.source, '_blank', 'noopener, noreferrer'); + }} + > +
+ + + + {res.title} + + + {processedResults.map((element, index) => ( + + + {element.tag === 'code' && } + {(element.tag === 'bulletList' || element.tag === 'numberedList') && } + {element.tag === 'text' && } + {element.tag === 'heading' && } + {element.tag === 'blockquote' && } + +
+ + ))} + + +
+ + ); + return null; + }) + ) : ( + No results found + ) + ) : ( + + )} + ) } @@ -402,4 +569,4 @@ export const SearchBar = ({
) -} \ No newline at end of file +} diff --git a/extensions/react-widget/src/types/index.ts b/extensions/react-widget/src/types/index.ts index cea9e43a..5438cec7 100644 --- a/extensions/react-widget/src/types/index.ts +++ b/extensions/react-widget/src/types/index.ts @@ -44,9 +44,10 @@ export interface WidgetCoreProps extends WidgetProps { export interface SearchBarProps { apiHost?: string; apiKey?: string; - theme?:THEME; - placeholder?:string; - width?:string; + theme?: THEME; + placeholder?: string; + width?: string; + buttonText?: string; } export interface Result { diff --git a/extensions/react-widget/src/utils/helper.ts b/extensions/react-widget/src/utils/helper.ts index d9aa19c3..9f92fdcb 100644 --- a/extensions/react-widget/src/utils/helper.ts +++ b/extensions/react-widget/src/utils/helper.ts @@ -1,5 +1,3 @@ -import MarkdownIt from "markdown-it"; -import DOMPurify from "dompurify"; export const getOS = () => { const platform = window.navigator.platform; const userAgent = window.navigator.userAgent || window.navigator.vendor; @@ -27,61 +25,127 @@ export const getOS = () => { return 'other'; }; -export const preprocessSearchResultsToHTML = (text: string, keyword: string) => { - const md = new MarkdownIt(); - const htmlString = md.render(text); +interface ParsedElement { + content: string; + tag: string; +} - // Container for processed HTML - const filteredResults = document.createElement("div"); - filteredResults.innerHTML = htmlString; +export const processMarkdownString = (markdown: string, keyword?: string): ParsedElement[] => { + const lines = markdown.trim().split('\n'); + const keywordLower = keyword?.toLowerCase(); - if (!processNode(filteredResults, keyword.trim())) return null; + const escapeRegExp = (str: string) => str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + const escapedKeyword = keyword ? escapeRegExp(keyword) : ''; + const keywordRegex = keyword ? new RegExp(`(${escapedKeyword})`, 'gi') : null; - return filteredResults.innerHTML.trim() ? filteredResults.outerHTML : null; -}; + let isInCodeBlock = false; + let codeBlockContent: string[] = []; + let matchingLines: ParsedElement[] = []; + let firstLine: ParsedElement | null = null; + for (let i = 0; i < lines.length; i++) { + const trimmedLine = lines[i].trim(); + if (!trimmedLine) continue; + if (trimmedLine.startsWith('```')) { + if (!isInCodeBlock) { + isInCodeBlock = true; + codeBlockContent = []; + } else { + isInCodeBlock = false; + const codeContent = codeBlockContent.join('\n'); + const parsedElement: ParsedElement = { + content: codeContent, + tag: 'code' + }; -// Recursive function to process nodes -const processNode = (node: Node, keyword: string): boolean => { + if (!firstLine) { + firstLine = parsedElement; + } - const keywordRegex = new RegExp(`(${keyword})`, "gi"); - if (node.nodeType === Node.TEXT_NODE) { - const textContent = node.textContent || ""; - - if (textContent.toLowerCase().includes(keyword.toLowerCase())) { - const highlightedHTML = textContent.replace( - keywordRegex, - `$1` - ); - const tempContainer = document.createElement("div"); - tempContainer.innerHTML = highlightedHTML; - - // Replace the text node with highlighted content - while (tempContainer.firstChild) { - node.parentNode?.insertBefore(tempContainer.firstChild, node); + if (keywordLower && codeContent.toLowerCase().includes(keywordLower)) { + parsedElement.content = parsedElement.content.replace(keywordRegex!, '$1'); + matchingLines.push(parsedElement); + } } - node.parentNode?.removeChild(node); - - return true; + continue; } - return false; - } else if (node.nodeType === Node.ELEMENT_NODE) { + if (isInCodeBlock) { + codeBlockContent.push(trimmedLine); + continue; + } - const children = Array.from(node.childNodes); - let hasKeyword = false; + let parsedElement: ParsedElement | null = null; - children.forEach((child) => { - if (!processNode(child, keyword)) { - node.removeChild(child); - } else { - hasKeyword = true; - } - }); + const headingMatch = trimmedLine.match(/^(#{1,6})\s+(.+)$/); + const bulletMatch = trimmedLine.match(/^[-*]\s+(.+)$/); + const numberedMatch = trimmedLine.match(/^\d+\.\s+(.+)$/); + const blockquoteMatch = trimmedLine.match(/^>+\s*(.+)$/); - return hasKeyword; + let content = trimmedLine; + + if (headingMatch) { + content = headingMatch[2]; + parsedElement = { + content: content, + tag: 'heading' + }; + } else if (bulletMatch) { + content = bulletMatch[1]; + parsedElement = { + content: content, + tag: 'bulletList' + }; + } else if (numberedMatch) { + content = numberedMatch[1]; + parsedElement = { + content: content, + tag: 'numberedList' + }; + } else if (blockquoteMatch) { + content = blockquoteMatch[1]; + parsedElement = { + content: content, + tag: 'blockquote' + }; + } else { + parsedElement = { + content: content, + tag: 'text' + }; + } + + if (!firstLine) { + firstLine = parsedElement; + } + + if (keywordLower && parsedElement.content.toLowerCase().includes(keywordLower)) { + parsedElement.content = parsedElement.content.replace(keywordRegex!, '$1'); + matchingLines.push(parsedElement); + } } - return false; -}; \ No newline at end of file + if (isInCodeBlock && codeBlockContent.length > 0) { + const codeContent = codeBlockContent.join('\n'); + const parsedElement: ParsedElement = { + content: codeContent, + tag: 'code' + }; + + if (!firstLine) { + firstLine = parsedElement; + } + + if (keywordLower && codeContent.toLowerCase().includes(keywordLower)) { + parsedElement.content = parsedElement.content.replace(keywordRegex!, '$1'); + matchingLines.push(parsedElement); + } + } + + if (keywordLower && matchingLines.length > 0) { + return matchingLines; + } + + return firstLine ? [firstLine] : []; +}; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 9a3dbf31..845463aa 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -11,15 +11,15 @@ "@reduxjs/toolkit": "^2.2.7", "chart.js": "^4.4.4", "i18next": "^24.2.0", - "i18next-browser-languagedetector": "^8.0.0", + "i18next-browser-languagedetector": "^8.0.2", "prop-types": "^15.8.1", "react": "^18.2.0", - "react-chartjs-2": "^5.2.0", + "react-chartjs-2": "^5.3.0", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^18.3.1", "react-dropzone": "^14.3.5", "react-helmet": "^6.1.0", - "react-i18next": "^15.0.2", + "react-i18next": "^15.4.0", "react-markdown": "^9.0.1", "react-redux": "^8.0.5", "react-router-dom": "^7.1.1", @@ -44,14 +44,14 @@ "eslint-plugin-n": "^15.7.0", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-promise": "^6.6.0", - "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react": "^7.37.3", "eslint-plugin-unused-imports": "^4.1.4", "husky": "^8.0.0", - "lint-staged": "^15.2.11", + "lint-staged": "^15.3.0", "postcss": "^8.4.49", - "prettier": "^3.3.3", - "prettier-plugin-tailwindcss": "^0.6.8", - "tailwindcss": "^3.4.15", + "prettier": "^3.4.2", + "prettier-plugin-tailwindcss": "^0.6.9", + "tailwindcss": "^3.4.17", "typescript": "^5.7.2", "vite": "^5.4.11", "vite-plugin-svgr": "^4.2.0" @@ -2181,13 +2181,13 @@ "dev": true }, "node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" }, "engines": { "node": ">= 0.4" @@ -2284,15 +2284,15 @@ } }, "node_modules/array.prototype.flatmap": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", - "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -2318,19 +2318,18 @@ } }, "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", "dev": true, "dependencies": { "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" }, "engines": { "node": ">= 0.4" @@ -2550,13 +2549,13 @@ } }, "node_modules/call-bound": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.2.tgz", - "integrity": "sha512-0lk0PHFe/uz0vl527fG9CgdE9WdafjDbCXvBbs+LUv000TVt2Jjhqbs4Jwm8gz070w8xXyEAxrPOMullsxXeGg==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", "dev": true, "dependencies": { - "call-bind": "^1.0.8", - "get-intrinsic": "^1.2.5" + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -2624,6 +2623,18 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/chalk": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", + "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/character-entities": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", @@ -2856,14 +2867,14 @@ "integrity": "sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==" }, "node_modules/data-view-buffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -2873,29 +2884,29 @@ } }, "node_modules/data-view-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "is-data-view": "^1.0.2" }, "engines": { "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/inspect-js" } }, "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.6", + "call-bound": "^1.0.2", "es-errors": "^1.3.0", "is-data-view": "^1.0.1" }, @@ -3047,12 +3058,12 @@ "dev": true }, "node_modules/dunder-proto": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.0.tgz", - "integrity": "sha512-9+Sj30DIu+4KvHqMfLUGLFYL2PkURSYMVXJyXe92nFRvlYq5hBjLEhblKB+vkd/WVlUYMWigiY07T91Fkk0+4A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "dev": true, "dependencies": { - "call-bind-apply-helpers": "^1.0.0", + "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" }, @@ -3111,57 +3122,62 @@ } }, "node_modules/es-abstract": { - "version": "1.23.5", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.5.tgz", - "integrity": "sha512-vlmniQ0WNPwXqA0BnmwV3Ng7HxiGlh6r5U6JcTMNx8OilcAGqVJBHJcPjqOMaczU9fRuRK5Px2BdVyPRnKMMVQ==", + "version": "1.23.9", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", + "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.0", + "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", - "gopd": "^1.0.1", + "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", + "is-data-view": "^1.0.2", + "is-regex": "^1.2.1", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.0", + "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", - "object.assign": "^4.1.5", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.18" }, "engines": { "node": ">= 0.4" @@ -3189,26 +3205,27 @@ } }, "node_modules/es-iterator-helpers": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.0.tgz", - "integrity": "sha512-tpxqxncxnpw3c93u8n3VOzACmRFoVmWJqbWXvX/JfKbkhBw1oslgPrUfeSt2psuqyEJFD6N/9lg5i7bsKpoq+Q==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.2.1.tgz", + "integrity": "sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "es-abstract": "^1.23.3", + "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-set-tostringtag": "^2.0.3", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", + "get-intrinsic": "^1.2.6", "globalthis": "^1.0.4", - "gopd": "^1.0.1", + "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.7", - "iterator.prototype": "^1.1.3", - "safe-array-concat": "^1.1.2" + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.4", + "safe-array-concat": "^1.1.3" }, "engines": { "node": ">= 0.4" @@ -3227,14 +3244,15 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.4", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -3250,14 +3268,14 @@ } }, "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", "dev": true, "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "is-callable": "^1.2.7", + "is-date-object": "^1.0.5", + "is-symbol": "^1.0.4" }, "engines": { "node": ">= 0.4" @@ -3671,28 +3689,28 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.37.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.2.tgz", - "integrity": "sha512-EsTAnj9fLVr/GZleBLFbj/sSuXeWmp1eXIN60ceYnZveqEaUCyW4X+Vh4WTdUhCkW4xutXYqTXCUSyqD4rB75w==", + "version": "7.37.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.3.tgz", + "integrity": "sha512-DomWuTQPFYZwF/7c9W2fkKkStqZmBd3uugfqBYLdkZ3Hii23WzZuOLUskGxB8qkSKqftxEeGL1TB2kMhrce0jA==", "dev": true, "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", - "array.prototype.flatmap": "^1.3.2", + "array.prototype.flatmap": "^1.3.3", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", - "es-iterator-helpers": "^1.1.0", + "es-iterator-helpers": "^1.2.1", "estraverse": "^5.3.0", "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.8", "object.fromentries": "^2.0.8", - "object.values": "^1.2.0", + "object.values": "^1.2.1", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.11", + "string.prototype.matchall": "^4.0.12", "string.prototype.repeat": "^1.0.0" }, "engines": { @@ -4273,15 +4291,17 @@ } }, "node_modules/function.prototype.name": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.6.tgz", - "integrity": "sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==", + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "functions-have-names": "^1.2.3" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "functions-have-names": "^1.2.3", + "hasown": "^2.0.2", + "is-callable": "^1.2.7" }, "engines": { "node": ">= 0.4" @@ -4321,21 +4341,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", - "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", "dev": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", - "dunder-proto": "^1.0.0", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", + "get-proto": "^1.0.0", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", - "math-intrinsics": "^1.0.0" + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -4344,6 +4364,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", @@ -4357,14 +4390,14 @@ } }, "node_modules/get-symbol-description": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", - "integrity": "sha512-g0QYk1dZBxGwk+Ngc+ltRH2IBp2f7zBkBMBJZCDerh6EhlhSR6+9irMCuT/09zD6qkarHUSn529sK/yL4S27mg==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4" + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -4475,10 +4508,13 @@ "dev": true }, "node_modules/has-bigints": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", - "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", "dev": true, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4496,10 +4532,13 @@ } }, "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==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", "dev": true, + "dependencies": { + "dunder-proto": "^1.0.0" + }, "engines": { "node": ">= 0.4" }, @@ -4956,10 +4995,9 @@ } }, "node_modules/i18next-browser-languagedetector": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz", - "integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==", - "license": "MIT", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.2.tgz", + "integrity": "sha512-shBvPmnIyZeD2VU5jVGIOWP7u9qNG3Lj7mpaiPFpbJ3LVfHZJvVzKR4v1Cb91wAOFpNw442N+LGPzHOHsten2g==", "dependencies": { "@babel/runtime": "^7.23.2" } @@ -5029,14 +5067,14 @@ "integrity": "sha512-qlD8YNDqyTKTyuITrDOffsl6Tdhv+UC4hcdAVuQsK4IMQ99nSgd1MIA/Q+jQYoh9r3hVUXhYh7urSRmXPkW04g==" }, "node_modules/internal-slot": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.7.tgz", - "integrity": "sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", "dev": true, "dependencies": { "es-errors": "^1.3.0", - "hasown": "^2.0.0", - "side-channel": "^1.0.4" + "hasown": "^2.0.2", + "side-channel": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -5065,13 +5103,14 @@ } }, "node_modules/is-array-buffer": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", - "integrity": "sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==", + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.2.1" + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -5087,12 +5126,15 @@ "dev": true }, "node_modules/is-async-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", - "integrity": "sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.0.tgz", + "integrity": "sha512-GExz9MtyhlZyXYLxzlJRj5WUCE661zhDa1Yna52CN57AJsymh+DvXXjyveSioqSRdxvUrdKdvqB1b5cVKsNpWQ==", "dev": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -5172,11 +5214,13 @@ } }, "node_modules/is-data-view": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.1.tgz", - "integrity": "sha512-AHkaJrsUVW6wq6JS8y3JnM/GJF/9cf+k20+iDzlSaJrinEo5+7vRiteOSwBhHRiAyQATN1AmY4hwzxJKPmYf+w==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", "dev": true, "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", "is-typed-array": "^1.1.13" }, "engines": { @@ -5221,12 +5265,12 @@ } }, "node_modules/is-finalizationregistry": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.0.tgz", - "integrity": "sha512-qfMdqbAQEwBw78ZyReKnlA8ezmPdb9BemzIIip/JkjaZUhitfXDkkr+3QTboW0JrSXT1QWyYShpvnNHGZ4c4yA==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", "dev": true, "dependencies": { - "call-bind": "^1.0.7" + "call-bound": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -5248,12 +5292,15 @@ } }, "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", "dev": true, "dependencies": { - "has-tostringtag": "^1.0.0" + "call-bound": "^1.0.3", + "get-proto": "^1.0.0", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -5295,18 +5342,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-negative-zero": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", - "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", - "dev": true, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -5317,12 +5352,12 @@ } }, "node_modules/is-number-object": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.0.tgz", - "integrity": "sha512-KVSZV0Dunv9DTPkhXwcZ3Q+tUc9TsaE1ZwX5J2WMvsSGS6Md8TFPun5uwh0yRdrNerI6vf/tbJxqSx4c1ZI1Lw==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" }, "engines": { @@ -5383,12 +5418,12 @@ } }, "node_modules/is-shared-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.3.tgz", - "integrity": "sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, "dependencies": { - "call-bind": "^1.0.7" + "call-bound": "^1.0.3" }, "engines": { "node": ">= 0.4" @@ -5410,12 +5445,12 @@ } }, "node_modules/is-string": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.0.tgz", - "integrity": "sha512-PlfzajuF9vSo5wErv3MJAKD/nqf9ngAs1NFQYm16nUYFO2IzxJ2hcm+IOCg+EEopdykNNUhVq5cz35cAUxU8+g==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" }, "engines": { @@ -5443,12 +5478,12 @@ } }, "node_modules/is-typed-array": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.13.tgz", - "integrity": "sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==", + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, "dependencies": { - "which-typed-array": "^1.1.14" + "which-typed-array": "^1.1.16" }, "engines": { "node": ">= 0.4" @@ -5470,25 +5505,28 @@ } }, "node_modules/is-weakref": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", - "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.0.tgz", + "integrity": "sha512-SXM8Nwyys6nT5WP6pltOwKytLV7FqQ4UiibxVmW+EIosHcmCqkkjViTb5SNssDlkCiEYRP1/pdWUKVvZBmsR2Q==", "dev": true, "dependencies": { - "call-bind": "^1.0.2" + "call-bound": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, "node_modules/is-weakset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.3.tgz", - "integrity": "sha512-LvIm3/KWzS9oRFHugab7d+M/GcBXuXX5xZkzPmN+NxihdQlZUQ4dWuSV1xR/sq6upL1TJEDrfBgRepHFdBtSNQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4" + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" }, "engines": { "node": ">= 0.4" @@ -5510,16 +5548,16 @@ "dev": true }, "node_modules/iterator.prototype": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.4.tgz", - "integrity": "sha512-x4WH0BWmrMmg4oHHl+duwubhrvczGlyuGAZu3nvrf0UXOfPu8IhZObFEr7DE/iv01YgVZrsOiRcqw2srkKEDIA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", "dev": true, "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", "has-symbols": "^1.1.0", - "reflect.getprototypeof": "^1.0.8", "set-function-name": "^2.0.2" }, "engines": { @@ -5661,12 +5699,15 @@ } }, "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", "dev": true, "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/lines-and-columns": { @@ -5676,12 +5717,12 @@ "dev": true }, "node_modules/lint-staged": { - "version": "15.2.11", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.11.tgz", - "integrity": "sha512-Ev6ivCTYRTGs9ychvpVw35m/bcNDuBN+mnTeObCL5h+boS5WzBEC6LHI4I9F/++sZm1m+J2LEiy0gxL/R9TBqQ==", + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.3.0.tgz", + "integrity": "sha512-vHFahytLoF2enJklgtOtCtIjZrKD/LoxlaUusd5nh7dWv/dkKQJY74ndFSzxCdv7g0ueGg1ORgTSt4Y9LPZn9A==", "dev": true, "dependencies": { - "chalk": "~5.3.0", + "chalk": "~5.4.1", "commander": "~12.1.0", "debug": "~4.4.0", "execa": "~8.0.1", @@ -5702,30 +5743,6 @@ "url": "https://opencollective.com/lint-staged" } }, - "node_modules/lint-staged/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/lint-staged/node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, "node_modules/listr2": { "version": "8.2.5", "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.5.tgz", @@ -5908,9 +5925,9 @@ } }, "node_modules/math-intrinsics": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", - "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, "engines": { "node": ">= 0.4" @@ -7112,14 +7129,16 @@ } }, "node_modules/object.assign": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.5.tgz", - "integrity": "sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "dev": true, "dependencies": { - "call-bind": "^1.0.5", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "has-symbols": "^1.0.3", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", "object-keys": "^1.1.1" }, "engines": { @@ -7176,12 +7195,13 @@ } }, "node_modules/object.values": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", - "integrity": "sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" }, @@ -7233,6 +7253,23 @@ "node": ">= 0.8.0" } }, + "node_modules/own-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.6", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -7557,18 +7594,6 @@ } } }, - "node_modules/postcss-load-config/node_modules/lilconfig": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", - "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, "node_modules/postcss-nested": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", @@ -7623,9 +7648,9 @@ } }, "node_modules/prettier": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", - "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.4.2.tgz", + "integrity": "sha512-e9MewbtFo+Fevyuxn/4rrcDAaq0IYxPGLvObpQjiZBMAzB9IGmzlnG9RZy3FFas+eBMu2vA0CszMeduow5dIuQ==", "dev": true, "bin": { "prettier": "bin/prettier.cjs" @@ -7650,9 +7675,9 @@ } }, "node_modules/prettier-plugin-tailwindcss": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.8.tgz", - "integrity": "sha512-dGu3kdm7SXPkiW4nzeWKCl3uoImdd5CTZEJGxyypEPL37Wj0HT2pLqjrvSei1nTeuQfO4PUfjeW5cTUNRLZ4sA==", + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.6.9.tgz", + "integrity": "sha512-r0i3uhaZAXYP0At5xGfJH876W3HHGHDp+LCRUJrs57PBeQ6mYHMwr25KH8NPX44F2yGTvdnH7OqCshlQx183Eg==", "dev": true, "engines": { "node": ">=14.21.3" @@ -7796,12 +7821,12 @@ } }, "node_modules/react-chartjs-2": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.2.0.tgz", - "integrity": "sha512-98iN5aguJyVSxp5U3CblRLH67J8gkfyGNbiK3c+l1QI/G4irHMPQw44aEPmjVag+YKTyQ260NcF82GTQ3bdscA==", + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz", + "integrity": "sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==", "peerDependencies": { "chart.js": "^4.1.1", - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/react-copy-to-clipboard": { @@ -7866,9 +7891,9 @@ } }, "node_modules/react-i18next": { - "version": "15.0.2", - "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.2.tgz", - "integrity": "sha512-z0W3/RES9Idv3MmJUcf0mDNeeMOUXe+xoL0kPfQPbDoZHmni/XsIoq5zgT2MCFUiau283GuBUK578uD/mkAbLQ==", + "version": "15.4.0", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.4.0.tgz", + "integrity": "sha512-Py6UkX3zV08RTvL6ZANRoBh9sL/ne6rQq79XlkHEdd82cZr2H9usbWpUNVadJntIZP2pu3M2rL1CN+5rQYfYFw==", "dependencies": { "@babel/runtime": "^7.25.0", "html-parse-stringify": "^3.0.1" @@ -8073,19 +8098,19 @@ } }, "node_modules/reflect.getprototypeof": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.8.tgz", - "integrity": "sha512-B5dj6usc5dkk8uFliwjwDHM8To5/QwdKz9JcBZ8Ic4G1f0YmeeJTtE/ZTdgRFPAfxZFiUaPhZ1Jcs4qeagItGQ==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", "dev": true, "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", - "dunder-proto": "^1.0.0", - "es-abstract": "^1.23.5", + "es-abstract": "^1.23.9", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "gopd": "^1.2.0", - "which-builtin-type": "^1.2.0" + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -8418,14 +8443,15 @@ } }, "node_modules/safe-array-concat": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.2.tgz", - "integrity": "sha512-vj6RsCsWBCf19jIeHEfkRMw8DPiBb+DMXklQ/1SGDHOMlHdPUkZXFQ2YdplS23zESTijAcurb1aSgJA3AgMu1Q==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", - "get-intrinsic": "^1.2.4", - "has-symbols": "^1.0.3", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "has-symbols": "^1.1.0", "isarray": "^2.0.5" }, "engines": { @@ -8435,6 +8461,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/safe-regex-test": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", @@ -8506,6 +8548,20 @@ "node": ">= 0.4" } }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -8528,15 +8584,69 @@ } }, "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==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" }, "engines": { "node": ">= 0.4" @@ -8700,23 +8810,24 @@ } }, "node_modules/string.prototype.matchall": { - "version": "4.0.11", - "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.11.tgz", - "integrity": "sha512-NUdh0aDavY2og7IbBPenWqR9exH+E26Sv8e0/eTe1tltDGZL+GtBkDAnnyBtmekfK6/Dq3MkcGtzXFEd1LQrtg==", + "version": "4.0.12", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz", + "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", + "es-abstract": "^1.23.6", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-symbols": "^1.0.3", - "internal-slot": "^1.0.7", - "regexp.prototype.flags": "^1.5.2", + "get-intrinsic": "^1.2.6", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.3", "set-function-name": "^2.0.2", - "side-channel": "^1.0.6" + "side-channel": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -8736,15 +8847,18 @@ } }, "node_modules/string.prototype.trim": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", - "integrity": "sha512-klHuCNxiMZ8MlsOihJhJEBJAiMVqU3Z2nEXWfWnIqjN0gEFS9J9+IxKozWWtQGcgoa1WUZzLjKPTr4ZHNFTFxw==", + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", + "define-data-property": "^1.1.4", "define-properties": "^1.2.1", - "es-abstract": "^1.23.0", - "es-object-atoms": "^1.0.0" + "es-abstract": "^1.23.5", + "es-object-atoms": "^1.0.0", + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" @@ -8754,15 +8868,19 @@ } }, "node_modules/string.prototype.trimend": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.8.tgz", - "integrity": "sha512-p73uL5VCHCO2BZZ6krwwQE3kCzM7NKmis8S//xEC6fQonchbum4eP6kR4DLEjQFO3Wnj3Fuo8NM0kOSjVdHjZQ==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.2", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -9002,9 +9120,9 @@ "dev": true }, "node_modules/tailwindcss": { - "version": "3.4.15", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.15.tgz", - "integrity": "sha512-r4MeXnfBmSOuKUWmXe6h2CcyfzJCEk4F0pptO5jlnYSIViUkVmsawj80N5h2lO3gwcmSb4n3PuN+e+GC1Guylw==", + "version": "3.4.17", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz", + "integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==", "dev": true, "dependencies": { "@alloc/quick-lru": "^5.2.0", @@ -9016,7 +9134,7 @@ "glob-parent": "^6.0.2", "is-glob": "^4.0.3", "jiti": "^1.21.6", - "lilconfig": "^2.1.0", + "lilconfig": "^3.1.3", "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", @@ -9181,30 +9299,30 @@ } }, "node_modules/typed-array-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz", - "integrity": "sha512-gEymJYKZtKXzzBzM4jqa9w6Q1Jjm7x2d+sh19AdsD4wqnMPDYyvwpsIc2Q/835kHuo3BEQ7CjelGhfTsoBb2MQ==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bound": "^1.0.3", "es-errors": "^1.3.0", - "is-typed-array": "^1.1.13" + "is-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" } }, "node_modules/typed-array-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.1.tgz", - "integrity": "sha512-3iMJ9q0ao7WE9tWcaYKIptkNBuOIcZCCT0d4MRvuuH88fEoEH62IuQe0OtraD3ebQEoTRk8XCBoknUNc1Y67pw==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" }, "engines": { "node": ">= 0.4" @@ -9214,17 +9332,18 @@ } }, "node_modules/typed-array-byte-offset": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.2.tgz", - "integrity": "sha512-Ous0vodHa56FviZucS2E63zkgtgrACj7omjwd/8lTEMEPFFyjfixMZ1ZXenpgCFBBt4EC1J2XsyVS2gkG0eTFA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", "dev": true, "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", "for-each": "^0.3.3", - "gopd": "^1.0.1", - "has-proto": "^1.0.3", - "is-typed-array": "^1.1.13" + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.9" }, "engines": { "node": ">= 0.4" @@ -9234,17 +9353,17 @@ } }, "node_modules/typed-array-length": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.6.tgz", - "integrity": "sha512-/OxDN6OtAk5KBpGb28T+HZc2M+ADtvRxXrKKbUwtsLgdoxgX13hyy7ek6bFRl5+aBs2yZzB0c4CnQfAtVypW/g==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", "dev": true, "dependencies": { "call-bind": "^1.0.7", "for-each": "^0.3.3", "gopd": "^1.0.1", - "has-proto": "^1.0.3", "is-typed-array": "^1.1.13", - "possible-typed-array-names": "^1.0.0" + "possible-typed-array-names": "^1.0.0", + "reflect.getprototypeof": "^1.0.6" }, "engines": { "node": ">= 0.4" @@ -9267,15 +9386,18 @@ } }, "node_modules/unbox-primitive": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", - "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", + "call-bound": "^1.0.3", "has-bigints": "^1.0.2", - "has-symbols": "^1.0.3", - "which-boxed-primitive": "^1.0.2" + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9591,16 +9713,16 @@ } }, "node_modules/which-boxed-primitive": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.0.tgz", - "integrity": "sha512-Ei7Miu/AXe2JJ4iNF5j/UphAgRoma4trE6PtisM09bPygb3egMH3YLW/befsWb1A1AxvNSFidOFTB18XtnIIng==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", "dev": true, "dependencies": { "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.0", - "is-number-object": "^1.1.0", - "is-string": "^1.1.0", - "is-symbol": "^1.1.0" + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" }, "engines": { "node": ">= 0.4" @@ -9655,15 +9777,16 @@ } }, "node_modules/which-typed-array": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.16.tgz", - "integrity": "sha512-g+N+GAWiRj66DngFwHvISJd+ITsyphZvD1vChfVg6cEdnzy53GzB3oy0fUNlvhz7H7+MiqhYr26qxQShCpKTTQ==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", + "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", "dev": true, "dependencies": { "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", "for-each": "^0.3.3", - "gopd": "^1.0.1", + "gopd": "^1.2.0", "has-tostringtag": "^1.0.2" }, "engines": { diff --git a/frontend/package.json b/frontend/package.json index ff98e94c..8ff27d9e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -22,15 +22,15 @@ "@reduxjs/toolkit": "^2.2.7", "chart.js": "^4.4.4", "i18next": "^24.2.0", - "i18next-browser-languagedetector": "^8.0.0", + "i18next-browser-languagedetector": "^8.0.2", "prop-types": "^15.8.1", "react": "^18.2.0", - "react-chartjs-2": "^5.2.0", + "react-chartjs-2": "^5.3.0", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^18.3.1", "react-helmet": "^6.1.0", "react-dropzone": "^14.3.5", - "react-i18next": "^15.0.2", + "react-i18next": "^15.4.0", "react-markdown": "^9.0.1", "react-redux": "^8.0.5", "react-router-dom": "^7.1.1", @@ -55,14 +55,14 @@ "eslint-plugin-n": "^15.7.0", "eslint-plugin-prettier": "^5.2.1", "eslint-plugin-promise": "^6.6.0", - "eslint-plugin-react": "^7.37.2", + "eslint-plugin-react": "^7.37.3", "eslint-plugin-unused-imports": "^4.1.4", "husky": "^8.0.0", - "lint-staged": "^15.2.11", + "lint-staged": "^15.3.0", "postcss": "^8.4.49", - "prettier": "^3.3.3", - "prettier-plugin-tailwindcss": "^0.6.8", - "tailwindcss": "^3.4.15", + "prettier": "^3.4.2", + "prettier-plugin-tailwindcss": "^0.6.9", + "tailwindcss": "^3.4.17", "typescript": "^5.7.2", "vite": "^5.4.11", "vite-plugin-svgr": "^4.2.0" diff --git a/frontend/src/Hero.tsx b/frontend/src/Hero.tsx index 644848dc..9fe965a1 100644 --- a/frontend/src/Hero.tsx +++ b/frontend/src/Hero.tsx @@ -37,12 +37,14 @@ export default function Hero({ ), diff --git a/frontend/src/Navigation.tsx b/frontend/src/Navigation.tsx index c29aaf20..bba83037 100644 --- a/frontend/src/Navigation.tsx +++ b/frontend/src/Navigation.tsx @@ -21,11 +21,10 @@ import { handleAbort, } from './conversation/conversationSlice'; import ConversationTile from './conversation/ConversationTile'; -import { useDarkTheme, useMediaQuery, useOutsideAlerter } from './hooks'; +import { useDarkTheme, useMediaQuery } from './hooks'; import useDefaultDocument from './hooks/useDefaultDocument'; import DeleteConvModal from './modals/DeleteConvModal'; import { ActiveState, Doc } from './models/misc'; -import APIKeyModal from './preferences/APIKeyModal'; import { getConversations, getDocs } from './preferences/preferenceApi'; import { selectApiKeyStatus, @@ -68,8 +67,6 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { const [isDocsListOpen, setIsDocsListOpen] = useState(false); const { t } = useTranslation(); const isApiKeySet = useSelector(selectApiKeyStatus); - const [apiKeyModalState, setApiKeyModalState] = - useState('INACTIVE'); const [uploadModalState, setUploadModalState] = useState('INACTIVE'); @@ -192,12 +189,6 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { console.error(err); }); } - useOutsideAlerter(navRef, () => { - if (isMobile && navOpen && apiKeyModalState === 'INACTIVE') { - setNavOpen(false); - setIsDocsListOpen(false); - } - }, [navOpen, isDocsListOpen, apiKeyModalState]); /* Needed to fix bug where if mobile nav was closed and then window was resized to desktop, nav would still be closed but the button to open would be gone, as per #1 on issue #146 @@ -220,7 +211,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { > menu toggle open new chat icon @@ -263,7 +254,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { }} > - + DocsGPT Logo

DocsGPT

@@ -275,7 +266,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { > menu toggle new

@@ -314,7 +305,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { Loading... )} @@ -365,6 +356,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { Upload document { setUploadModalState('ACTIVE'); if (isMobile) { @@ -392,7 +384,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { > icon

@@ -414,7 +406,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { > discord @@ -427,7 +419,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { > x @@ -440,7 +432,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { > github @@ -457,18 +449,13 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) { > menu toggle

DocsGPT
- => apiClient.post(endpoints.USER.UPDATE_TOOL, data), + deleteTool: (data: any): Promise => + apiClient.post(endpoints.USER.DELETE_TOOL, data), }; export default userService; diff --git a/frontend/src/assets/no-files-dark.svg b/frontend/src/assets/no-files-dark.svg new file mode 100644 index 00000000..b1e28a0f --- /dev/null +++ b/frontend/src/assets/no-files-dark.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/no-files.svg b/frontend/src/assets/no-files.svg new file mode 100644 index 00000000..36510546 --- /dev/null +++ b/frontend/src/assets/no-files.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/frontend/src/components/DocumentPagination.tsx b/frontend/src/components/DocumentPagination.tsx index f02ef1c0..6958d051 100644 --- a/frontend/src/components/DocumentPagination.tsx +++ b/frontend/src/components/DocumentPagination.tsx @@ -1,4 +1,5 @@ import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; import SingleArrowLeft from '../assets/single-left-arrow.svg'; import SingleArrowRight from '../assets/single-right-arrow.svg'; import DoubleArrowLeft from '../assets/double-arrow-left.svg'; @@ -19,6 +20,7 @@ const Pagination: React.FC = ({ onPageChange, onRowsPerPageChange, }) => { + const { t } = useTranslation(); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const rowsPerPageOptions = [5, 10, 20, 50]; @@ -53,7 +55,9 @@ const Pagination: React.FC = ({
{/* Rows per page dropdown */}
- Rows per page: + + {t('pagination.rowsPerPage')}: +
@@ -108,7 +112,7 @@ const Pagination: React.FC = ({ > Previous page @@ -119,7 +123,7 @@ const Pagination: React.FC = ({ > Next page @@ -130,7 +134,7 @@ const Pagination: React.FC = ({ > Last page diff --git a/frontend/src/components/SettingsBar.tsx b/frontend/src/components/SettingsBar.tsx index c6970600..17e7bf5e 100644 --- a/frontend/src/components/SettingsBar.tsx +++ b/frontend/src/components/SettingsBar.tsx @@ -59,7 +59,8 @@ const SettingsBar = ({ setActiveTab, activeTab }: SettingsBarProps) => {
@@ -67,16 +68,22 @@ const SettingsBar = ({ setActiveTab, activeTab }: SettingsBarProps) => {
{tabs.map((tab, index) => ( @@ -85,7 +92,8 @@ const SettingsBar = ({ setActiveTab, activeTab }: SettingsBarProps) => {
diff --git a/frontend/src/conversation/Conversation.tsx b/frontend/src/conversation/Conversation.tsx index 8a36ea5f..f4511fc3 100644 --- a/frontend/src/conversation/Conversation.tsx +++ b/frontend/src/conversation/Conversation.tsx @@ -386,13 +386,19 @@ export default function Conversation() { {...getRootProps()} className="flex w-full items-center rounded-[40px] border border-silver bg-white dark:bg-raisin-black" > - + + + {status === 'loading' ? ( + alt={t('loading')} + /> ) : (
- handleQuestionSubmission()} - src={isDarkTheme ? SendDark : Send} - > + aria-label={t('send')} + className="flex items-center justify-center" + > + {t('send')} +
)}
diff --git a/frontend/src/conversation/ConversationBubble.tsx b/frontend/src/conversation/ConversationBubble.tsx index d88b249e..668b0935 100644 --- a/frontend/src/conversation/ConversationBubble.tsx +++ b/frontend/src/conversation/ConversationBubble.tsx @@ -8,6 +8,7 @@ import { vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism'; import rehypeKatex from 'rehype-katex'; import remarkGfm from 'remark-gfm'; import remarkMath from 'remark-math'; +import { useTranslation } from 'react-i18next'; import DocsGPT3 from '../assets/cute_docsgpt3.svg'; import Dislike from '../assets/dislike.svg?react'; @@ -62,6 +63,7 @@ const ConversationBubble = forwardRef< }, ref, ) { + const { t } = useTranslation(); // const bubbleRef = useRef(null); const chunks = useSelector(selectChunks); const selectedDocs = useSelector(selectSelectedDocs); @@ -113,13 +115,13 @@ const ConversationBubble = forwardRef< {isEditClicked && (
@@ -68,9 +80,11 @@ function AddPrompt({ onClick={handleAddPrompt} className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:opacity-90" disabled={disableSave} - title={disableSave && newPromptName ? 'Name already exists' : ''} + title={ + disableSave && newPromptName ? t('modals.prompts.nameExists') : '' + } > - Save + {t('modals.prompts.save')}
@@ -97,6 +111,8 @@ function EditPrompt({ currentPromptEdit: { name: string; id: string; type: string }; disableSave: boolean; }) { + const { t } = useTranslation(); + return (

- Edit Prompt + {t('modals.prompts.editPrompt')}

- Edit your custom prompt and save it to DocsGPT + {t('modals.prompts.editDescription')}

+ setEditPromptName(e.target.value)} - > + />
- Prompt Name + {t('modals.prompts.promptName')}
- Prompt Text + {t('modals.prompts.promptText')}
+
@@ -150,9 +175,13 @@ function EditPrompt({ handleEditPrompt(currentPromptEdit.id, currentPromptEdit.type); }} disabled={currentPromptEdit.type === 'public' || disableSave} - title={disableSave && editPromptName ? 'Name already exists' : ''} + title={ + disableSave && editPromptName + ? t('modals.prompts.nameExists') + : '' + } > - Save + {t('modals.prompts.save')}
diff --git a/frontend/src/settings/APIKeys.tsx b/frontend/src/settings/APIKeys.tsx index 6775ba87..038e4bbb 100644 --- a/frontend/src/settings/APIKeys.tsx +++ b/frontend/src/settings/APIKeys.tsx @@ -115,12 +115,20 @@ export default function APIKeys() { - - + - - + + @@ -146,7 +154,7 @@ export default function APIKeys() {
{t('settings.apiKeys.name')} + + {t('settings.apiKeys.name')} + {t('settings.apiKeys.sourceDoc')} {t('settings.apiKeys.key')} + {t('settings.apiKeys.key')} +
Delete handleDeleteKey(element.id)} diff --git a/frontend/src/settings/Analytics.tsx b/frontend/src/settings/Analytics.tsx index 8baad361..441f6b19 100644 --- a/frontend/src/settings/Analytics.tsx +++ b/frontend/src/settings/Analytics.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; import { BarElement, CategoryScale, @@ -28,15 +29,29 @@ ChartJS.register( Legend, ); -const filterOptions = [ - { label: 'Hour', value: 'last_hour' }, - { label: '24 Hours', value: 'last_24_hour' }, - { label: '7 Days', value: 'last_7_days' }, - { label: '15 Days', value: 'last_15_days' }, - { label: '30 Days', value: 'last_30_days' }, -]; - export default function Analytics() { + const { t } = useTranslation(); + + const filterOptions = [ + { label: t('settings.analytics.filterOptions.hour'), value: 'last_hour' }, + { + label: t('settings.analytics.filterOptions.last24Hours'), + value: 'last_24_hour', + }, + { + label: t('settings.analytics.filterOptions.last7Days'), + value: 'last_7_days', + }, + { + label: t('settings.analytics.filterOptions.last15Days'), + value: 'last_15_days', + }, + { + label: t('settings.analytics.filterOptions.last30Days'), + value: 'last_30_days', + }, + ]; + const [messagesData, setMessagesData] = useState({ label: '30 Days', value: 'last_30_days' }); + }>({ + label: t('settings.analytics.filterOptions.last30Days'), + value: 'last_30_days', + }); const [tokenUsageFilter, setTokenUsageFilter] = useState<{ label: string; value: string; - }>({ label: '30 Days', value: 'last_30_days' }); + }>({ + label: t('settings.analytics.filterOptions.last30Days'), + value: 'last_30_days', + }); const [feedbackFilter, setFeedbackFilter] = useState<{ label: string; value: string; - }>({ label: '30 Days', value: 'last_30_days' }); + }>({ + label: t('settings.analytics.filterOptions.last30Days'), + value: 'last_30_days', + }); const [loadingMessages, setLoadingMessages] = useState(true); const [loadingTokens, setLoadingTokens] = useState(true); @@ -165,7 +189,7 @@ export default function Analytics() {

- Filter by chatbot + {t('settings.analytics.filterByChatbot')}

{ setSelectedChatbot( chatbots.find((item) => item.id === chatbot.value), @@ -191,6 +215,7 @@ export default function Analytics() { } rounded="3xl" border="border" + borderColor="gray-700" />
@@ -199,12 +224,12 @@ export default function Analytics() {

- Messages + {t('settings.analytics.messages')}

- Token Usage + {t('settings.analytics.tokenUsage')}

- Feedback + {t('settings.analytics.feedback')}

item.positive, ), backgroundColor: '#7D54D1', }, { - label: 'Negative Feedback', + label: t('settings.analytics.negativeFeedback'), data: Object.values(feedbackData || {}).map( (item) => item.negative, ), diff --git a/frontend/src/settings/Documents.tsx b/frontend/src/settings/Documents.tsx index 8e68f226..ee04d121 100644 --- a/frontend/src/settings/Documents.tsx +++ b/frontend/src/settings/Documents.tsx @@ -54,10 +54,10 @@ const Documents: React.FC = ({ const [totalPages, setTotalPages] = useState(1); const currentDocuments = paginatedDocuments ?? []; const syncOptions = [ - { label: 'Never', value: 'never' }, - { label: 'Daily', value: 'daily' }, - { label: 'Weekly', value: 'weekly' }, - { label: 'Monthly', value: 'monthly' }, + { label: t('settings.documents.syncFrequency.never'), value: 'never' }, + { label: t('settings.documents.syncFrequency.daily'), value: 'daily' }, + { label: t('settings.documents.syncFrequency.weekly'), value: 'weekly' }, + { label: t('settings.documents.syncFrequency.monthly'), value: 'monthly' }, ]; const refreshDocs = useCallback( @@ -151,9 +151,12 @@ const Documents: React.FC = ({
+ = ({ onChange={(e) => { setSearchTerm(e.target.value); setCurrentPage(1); - // refreshDocs(sortField, 1, rowsPerPage); - // do not call refreshDocs here the state is async - // so it will not have the updated value - }} // Handle search input change + }} />
{loading ? ( ) : ( -
+
@@ -224,9 +224,10 @@ const Documents: React.FC = ({ */} @@ -270,7 +271,7 @@ const Documents: React.FC = ({ {document.type !== 'remote' && ( Delete { @@ -282,7 +283,7 @@ const Documents: React.FC = ({ {document.syncFrequency && (
{ handleManageSync(document, value); diff --git a/frontend/src/settings/General.tsx b/frontend/src/settings/General.tsx index 4868d252..d974eab7 100644 --- a/frontend/src/settings/General.tsx +++ b/frontend/src/settings/General.tsx @@ -19,35 +19,20 @@ import Prompts from './Prompts'; export default function General() { const { t, - i18n: { changeLanguage, language }, + i18n: { changeLanguage }, } = useTranslation(); - const themes = ['Light', 'Dark']; + const themes = [ + { value: 'Light', label: t('settings.general.light') }, + { value: 'Dark', label: t('settings.general.dark') }, + ]; const languageOptions = [ - { - label: 'English', - value: 'en', - }, - { - label: 'Spanish', - value: 'es', - }, - { - label: 'Japanese', - value: 'jp', - }, - { - label: 'Mandarin', - value: 'zh', - }, - { - label: 'Traditional Chinese', - value: 'zhTW', - }, - { - label: 'Russian', - value: 'ru', - }, + { label: 'English', value: 'en' }, + { label: 'Español', value: 'es' }, + { label: '日本語', value: 'jp' }, + { label: '普通话', value: 'zh' }, + { label: '繁體中文(臺灣)', value: 'zhTW' }, + { label: 'Русский', value: 'ru' }, ]; const chunks = ['0', '2', '4', '6', '8', '10']; const token_limits = new Map([ @@ -99,15 +84,17 @@ export default function General() { return (
-

+

+ { - setSelectedTheme(option); - option !== selectedTheme && toggleTheme(); + selectedValue={ + themes.find((theme) => theme.value === selectedTheme) || null + } + onSelect={(option: { value: string; label: string }) => { + setSelectedTheme(option.value); + option.value !== selectedTheme && toggleTheme(); }} size="w-56" rounded="3xl" @@ -115,9 +102,9 @@ export default function General() { />
-

+

+ @@ -133,9 +120,9 @@ export default function General() { />
-

+

+
-

+

+ ({ value: value, @@ -181,16 +168,14 @@ export default function General() { />
-

+

+
diff --git a/frontend/src/settings/Logs.tsx b/frontend/src/settings/Logs.tsx index 1e248d46..9ff577b3 100644 --- a/frontend/src/settings/Logs.tsx +++ b/frontend/src/settings/Logs.tsx @@ -1,4 +1,5 @@ import React, { useState, useEffect, useRef, useCallback } from 'react'; +import { useTranslation } from 'react-i18next'; import userService from '../api/services/userService'; import ChevronRight from '../assets/chevron-right.svg'; @@ -8,6 +9,7 @@ import { APIKeyData, LogData } from './types'; import CoppyButton from '../components/CopyButton'; export default function Logs() { + const { t } = useTranslation(); const [chatbots, setChatbots] = useState([]); const [selectedChatbot, setSelectedChatbot] = useState(); const [logs, setLogs] = useState([]); @@ -65,9 +67,12 @@ export default function Logs() {
-

- Filter by chatbot -

+ {loadingChatbots ? ( ) : ( @@ -78,9 +83,9 @@ export default function Logs() { label: chatbot.name, value: chatbot.id, })), - { label: 'None', value: '' }, + { label: t('settings.logs.none'), value: '' }, ]} - placeholder="Select chatbot" + placeholder={t('settings.logs.selectChatbot')} onSelect={(chatbot: { label: string; value: string }) => { setSelectedChatbot( chatbots.find((item) => item.id === chatbot.value), @@ -120,6 +125,7 @@ type LogsTableProps = { }; function LogsTable({ logs, setPage }: LogsTableProps) { + const { t } = useTranslation(); const observerRef = useRef(); const firstObserver = useCallback((node: HTMLDivElement) => { if (observerRef.current) { @@ -134,7 +140,7 @@ function LogsTable({ logs, setPage }: LogsTableProps) {

- API generated / chatbot conversations + {t('settings.logs.tableHeader')}

chevron-right diff --git a/frontend/src/settings/Prompts.tsx b/frontend/src/settings/Prompts.tsx index 6e1810e5..611b0b90 100644 --- a/frontend/src/settings/Prompts.tsx +++ b/frontend/src/settings/Prompts.tsx @@ -168,7 +168,7 @@ export default function Prompts({ />
+
+ + +
@@ -118,7 +132,7 @@ export default function ToolConfig({ key={actionIndex} className="w-full border border-silver dark:border-silver/40 rounded-xl" > -
+

{action.name}

@@ -146,7 +160,7 @@ export default function ToolConfig({
-
+
('INACTIVE'); @@ -66,86 +72,113 @@ export default function Tools() {
+ setSearchTerm(e.target.value)} />
- {userTools - .filter((tool) => - tool.displayName - .toLowerCase() - .includes(searchTerm.toLowerCase()), - ) - .map((tool, index) => ( -
-
-
- - + +
+
+

+ {tool.displayName} +

+

+ {tool.description} +

+
-
-

- {tool.displayName} -

-

- {tool.description} -

+
+
-
- -
-
- ))} + )) + )}
); case t('settings.apiKeys.label'): diff --git a/frontend/src/upload/Upload.tsx b/frontend/src/upload/Upload.tsx index 7b80c592..59a2bf93 100644 --- a/frontend/src/upload/Upload.tsx +++ b/frontend/src/upload/Upload.tsx @@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next'; import { useDispatch, useSelector } from 'react-redux'; import userService from '../api/services/userService'; -import ArrowLeft from '../assets/arrow-left.svg'; import FileUpload from '../assets/file_upload.svg'; import WebsiteCollect from '../assets/website_collect.svg'; import Dropdown from '../components/Dropdown'; @@ -55,11 +54,11 @@ function Upload({ const setTimeoutRef = useRef(); const urlOptions: { label: string; value: string }[] = [ - { label: 'Crawler', value: 'crawler' }, - // { label: 'Sitemap', value: 'sitemap' }, - { label: 'Link', value: 'url' }, - { label: 'Reddit', value: 'reddit' }, - { label: 'GitHub', value: 'github' }, // P3f93 + { label: `Crawler`, value: 'crawler' }, + // { label: t('modals.uploadDoc.sitemap'), value: 'sitemap' }, + { label: `Link`, value: 'url' }, + { label: `GitHub`, value: 'github' }, + { label: `Reddit`, value: 'reddit' }, ]; const [urlType, setUrlType] = useState<{ label: string; value: string }>({ @@ -114,12 +113,14 @@ function Upload({

{isTraining && - (progress?.percentage === 100 ? 'Training completed' : title)} + (progress?.percentage === 100 + ? t('modals.uploadDoc.progress.completed') + : title)} {!isTraining && title}

-

This may take several minutes

+

{t('modals.uploadDoc.progress.wait')}

- Over the token limit, please consider uploading smaller document + {t('modals.uploadDoc.progress.tokenLimit')}

{/*

{progress?.percentage || 0}%

*/} @@ -149,7 +150,7 @@ function Upload({ } function UploadProgress() { - return ; + return ; } function TrainingProgress() { @@ -240,7 +241,7 @@ function Upload({ }, [progress, dispatch]); return (
)} - {activeTab && ( -
- {activeTab === 'file' ? ( - - ) : ( - - )} +
+ {activeTab && ( -
- )} + )} + +
); }
- {' '} + {t('settings.documents.actions')}