mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 16:43:16 +00:00
Merge branch 'main' into googleai-compatability-tools
This commit is contained in:
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: arc53
|
||||
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@@ -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"
|
||||
|
||||
@@ -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! 🚀
|
||||
|
||||
@@ -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
|
||||
|
||||
return {"success": True}, 200
|
||||
@@ -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
|
||||
139
application/parser/remote/crawler_markdown.py
Normal file
139
application/parser/remote/crawler_markdown.py
Normal file
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
yarl==1.18.3
|
||||
markdownify==0.14.1
|
||||
tldextract==5.1.3
|
||||
@@ -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(
|
||||
|
||||
103
docs/package-lock.json
generated
103
docs/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
4
extensions/react-widget/package-lock.json
generated
4
extensions/react-widget/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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---"
|
||||
# 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
|
||||
|
||||
@@ -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<string>("");
|
||||
const [loading, setLoading] = React.useState<boolean>(false);
|
||||
const [isWidgetOpen, setIsWidgetOpen] = React.useState<boolean>(false);
|
||||
const inputRef = React.useRef<HTMLInputElement>(null);
|
||||
const containerRef = React.useRef<HTMLInputElement>(null);
|
||||
const [isResultVisible, setIsResultVisible] = React.useState<boolean>(true);
|
||||
const [isResultVisible, setIsResultVisible] = React.useState<boolean>(false);
|
||||
const [results, setResults] = React.useState<Result[]>([]);
|
||||
const debounceTimeout = React.useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
const abortControllerRef = React.useRef<AbortController | null>(null)
|
||||
const abortControllerRef = React.useRef<AbortController | null>(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 (
|
||||
<ThemeProvider theme={{ ...themes[theme] }}>
|
||||
<Main>
|
||||
<GlobalStyle />
|
||||
<Container ref={containerRef}>
|
||||
<TextField
|
||||
spellCheck={false}
|
||||
<SearchButton
|
||||
onClick={() => 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}
|
||||
</SearchButton>
|
||||
{
|
||||
input.length > 0 && isResultVisible && (
|
||||
isResultVisible && (
|
||||
<SearchResults>
|
||||
<InfoButton onClick={openWidget}>
|
||||
{
|
||||
isTouch ?
|
||||
"Ask the AI" :
|
||||
<>
|
||||
Press <span style={{ fontSize: "16px" }}>↵</span> Enter to ask the AI
|
||||
</>
|
||||
}
|
||||
</InfoButton>
|
||||
{!loading ?
|
||||
(results.length > 0 ?
|
||||
results.map((res, key) => {
|
||||
const containsSource = res.source !== 'local';
|
||||
const filteredResults = preprocessSearchResultsToHTML(res.text,input)
|
||||
if (filteredResults)
|
||||
return (
|
||||
<ResultWrapper
|
||||
key={key}
|
||||
onClick={() => {
|
||||
if (!containsSource) return;
|
||||
window.open(res.source, '_blank', 'noopener, noreferrer')
|
||||
}}
|
||||
className={containsSource ? "contains-source" : ""}>
|
||||
<Title>{res.title}</Title>
|
||||
<Content>
|
||||
<Markdown
|
||||
dangerouslySetInnerHTML={{ __html: filteredResults }}
|
||||
/>
|
||||
</Content>
|
||||
</ResultWrapper>
|
||||
)
|
||||
else {
|
||||
setResults((prevItems) => prevItems.filter((_, index) => index !== key));
|
||||
}
|
||||
})
|
||||
:
|
||||
<NoResults>No results</NoResults>
|
||||
)
|
||||
:
|
||||
<Loader />
|
||||
}
|
||||
<SearchHeader>
|
||||
<TextField
|
||||
ref={inputRef}
|
||||
value={input}
|
||||
onChange={(e) => setInput(e.target.value)}
|
||||
onKeyDown={(e) => handleKeyDown(e)}
|
||||
placeholder={placeholder}
|
||||
autoFocus
|
||||
/>
|
||||
<EscapeInstruction onClick={() => setIsResultVisible(false)}>
|
||||
Esc
|
||||
</EscapeInstruction>
|
||||
</SearchHeader>
|
||||
<AskAIButton onClick={openWidget}>
|
||||
<img
|
||||
src="https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png"
|
||||
alt="DocsGPT"
|
||||
width={24}
|
||||
height={24}
|
||||
/>
|
||||
<span>Ask the AI</span>
|
||||
</AskAIButton>
|
||||
<SearchResultsScroll>
|
||||
{!loading ? (
|
||||
results.length > 0 ? (
|
||||
results.map((res, key) => {
|
||||
const containsSource = res.source !== 'local';
|
||||
const processedResults = processMarkdownString(res.text, input);
|
||||
if (processedResults)
|
||||
return (
|
||||
<ResultWrapper
|
||||
key={key}
|
||||
onClick={() => {
|
||||
if (!containsSource) return;
|
||||
window.open(res.source, '_blank', 'noopener, noreferrer');
|
||||
}}
|
||||
>
|
||||
<div style={{ flex: 1 }}>
|
||||
<ContentWrapper>
|
||||
<IconTitleWrapper>
|
||||
<ReaderIcon className="title-icon" />
|
||||
<Title>{res.title}</Title>
|
||||
</IconTitleWrapper>
|
||||
<Content>
|
||||
{processedResults.map((element, index) => (
|
||||
<ContentSegment key={index}>
|
||||
<IconTitleWrapper>
|
||||
{element.tag === 'code' && <CodeIcon className="element-icon" />}
|
||||
{(element.tag === 'bulletList' || element.tag === 'numberedList') && <ListBulletIcon className="element-icon" />}
|
||||
{element.tag === 'text' && <TextAlignLeftIcon className="element-icon" />}
|
||||
{element.tag === 'heading' && <HeadingIcon className="element-icon" />}
|
||||
{element.tag === 'blockquote' && <QuoteIcon className="element-icon" />}
|
||||
</IconTitleWrapper>
|
||||
<div
|
||||
style={{ flex: 1 }}
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(element.content),
|
||||
}}
|
||||
/>
|
||||
</ContentSegment>
|
||||
))}
|
||||
</Content>
|
||||
</ContentWrapper>
|
||||
</div>
|
||||
</ResultWrapper>
|
||||
);
|
||||
return null;
|
||||
})
|
||||
) : (
|
||||
<NoResults>No results found</NoResults>
|
||||
)
|
||||
) : (
|
||||
<Loader />
|
||||
)}
|
||||
</SearchResultsScroll>
|
||||
</SearchResults>
|
||||
)
|
||||
}
|
||||
@@ -402,4 +569,4 @@ export const SearchBar = ({
|
||||
</Main>
|
||||
</ThemeProvider>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
`<mark>$1</mark>`
|
||||
);
|
||||
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!, '<span class="highlight">$1</span>');
|
||||
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!, '<span class="highlight">$1</span>');
|
||||
matchingLines.push(parsedElement);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
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!, '<span class="highlight">$1</span>');
|
||||
matchingLines.push(parsedElement);
|
||||
}
|
||||
}
|
||||
|
||||
if (keywordLower && matchingLines.length > 0) {
|
||||
return matchingLines;
|
||||
}
|
||||
|
||||
return firstLine ? [firstLine] : [];
|
||||
};
|
||||
|
||||
863
frontend/package-lock.json
generated
863
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
|
||||
@@ -37,12 +37,14 @@ export default function Hero({
|
||||
<Fragment key={key}>
|
||||
<button
|
||||
onClick={() => handleQuestion({ question: demo.query })}
|
||||
className="w-full rounded-full border border-silver px-6 py-4 text-left hover:border-gray-4000 dark:hover:border-gray-3000 xl:min-w-[24vw]"
|
||||
className="w-full rounded-full border border-silver px-6 py-4 text-left hover:border-gray-4000 dark:hover:border-gray-3000 xl:min-w-[24vw] bg-white dark:bg-raisin-black focus:outline-none focus:ring-2 focus:ring-purple-taupe"
|
||||
>
|
||||
<p className="mb-1 font-semibold text-black dark:text-silver">
|
||||
<p className="mb-1 font-semibold text-black-1000 dark:text-bright-gray">
|
||||
{demo.header}
|
||||
</p>
|
||||
<span className="text-gray-400">{demo.query}</span>
|
||||
<span className="text-gray-700 dark:text-gray-300">
|
||||
{demo.query}
|
||||
</span>
|
||||
</button>
|
||||
</Fragment>
|
||||
),
|
||||
|
||||
@@ -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<ActiveState>('INACTIVE');
|
||||
|
||||
const [uploadModalState, setUploadModalState] =
|
||||
useState<ActiveState>('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) {
|
||||
>
|
||||
<img
|
||||
src={Expand}
|
||||
alt="menu toggle"
|
||||
alt="Toggle navigation menu"
|
||||
className={`${
|
||||
!navOpen ? 'rotate-180' : 'rotate-0'
|
||||
} m-auto transition-all duration-200`}
|
||||
@@ -234,7 +225,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
>
|
||||
<img
|
||||
src={openNewChat}
|
||||
alt="open new chat icon"
|
||||
alt="Start new chat"
|
||||
className="cursor-pointer"
|
||||
/>
|
||||
</button>
|
||||
@@ -263,7 +254,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
}}
|
||||
>
|
||||
<a href="/" className="flex gap-1.5">
|
||||
<img className="mb-2 h-10" src={DocsGPT3} alt="" />
|
||||
<img className="mb-2 h-10" src={DocsGPT3} alt="DocsGPT Logo" />
|
||||
<p className="my-auto text-2xl font-semibold">DocsGPT</p>
|
||||
</a>
|
||||
</div>
|
||||
@@ -275,7 +266,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
>
|
||||
<img
|
||||
src={Expand}
|
||||
alt="menu toggle"
|
||||
alt="Toggle navigation menu"
|
||||
className={`${
|
||||
!navOpen ? 'rotate-180' : 'rotate-0'
|
||||
} m-auto transition-all duration-200`}
|
||||
@@ -298,7 +289,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
>
|
||||
<img
|
||||
src={Add}
|
||||
alt="new"
|
||||
alt="Create new chat"
|
||||
className="opacity-80 group-hover:opacity-100"
|
||||
/>
|
||||
<p className=" text-sm text-dove-gray group-hover:text-neutral-600 dark:text-chinese-silver dark:group-hover:text-bright-gray">
|
||||
@@ -314,7 +305,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
<img
|
||||
src={isDarkTheme ? SpinnerDark : Spinner}
|
||||
className="animate-spin cursor-pointer bg-transparent"
|
||||
alt="Loading..."
|
||||
alt="Loading conversations"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
@@ -365,6 +356,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
<img
|
||||
className="mt-2 h-9 w-9 hover:cursor-pointer"
|
||||
src={UploadIcon}
|
||||
alt="Upload document"
|
||||
onClick={() => {
|
||||
setUploadModalState('ACTIVE');
|
||||
if (isMobile) {
|
||||
@@ -392,7 +384,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
>
|
||||
<img
|
||||
src={SettingGear}
|
||||
alt="icon"
|
||||
alt="Settings"
|
||||
className="ml-2 w-5 filter dark:invert"
|
||||
/>
|
||||
<p className="my-auto text-sm text-eerie-black dark:text-white">
|
||||
@@ -414,7 +406,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
>
|
||||
<img
|
||||
src={Discord}
|
||||
alt="discord"
|
||||
alt="Join Discord community"
|
||||
className="m-2 w-6 self-center filter dark:invert"
|
||||
/>
|
||||
</NavLink>
|
||||
@@ -427,7 +419,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
>
|
||||
<img
|
||||
src={Twitter}
|
||||
alt="x"
|
||||
alt="Follow us on Twitter"
|
||||
className="m-2 w-5 self-center filter dark:invert"
|
||||
/>
|
||||
</NavLink>
|
||||
@@ -440,7 +432,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
>
|
||||
<img
|
||||
src={Github}
|
||||
alt="github"
|
||||
alt="View on GitHub"
|
||||
className="m-2 w-6 self-center filter dark:invert"
|
||||
/>
|
||||
</NavLink>
|
||||
@@ -457,18 +449,13 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
>
|
||||
<img
|
||||
src={Hamburger}
|
||||
alt="menu toggle"
|
||||
alt="Toggle mobile menu"
|
||||
className="w-7 filter dark:invert"
|
||||
/>
|
||||
</button>
|
||||
<div className="text-[#949494] font-medium text-[20px]">DocsGPT</div>
|
||||
</div>
|
||||
</div>
|
||||
<APIKeyModal
|
||||
modalState={apiKeyModalState}
|
||||
setModalState={setApiKeyModalState}
|
||||
isCancellable={isApiKeySet}
|
||||
/>
|
||||
<DeleteConvModal
|
||||
modalState={modalStateDeleteConv}
|
||||
setModalState={setModalStateDeleteConv}
|
||||
|
||||
@@ -23,6 +23,7 @@ const endpoints = {
|
||||
CREATE_TOOL: '/api/create_tool',
|
||||
UPDATE_TOOL_STATUS: '/api/update_tool_status',
|
||||
UPDATE_TOOL: '/api/update_tool',
|
||||
DELETE_TOOL: '/api/delete_tool',
|
||||
},
|
||||
CONVERSATION: {
|
||||
ANSWER: '/api/answer',
|
||||
|
||||
@@ -45,6 +45,8 @@ const userService = {
|
||||
apiClient.post(endpoints.USER.UPDATE_TOOL_STATUS, data),
|
||||
updateTool: (data: any): Promise<any> =>
|
||||
apiClient.post(endpoints.USER.UPDATE_TOOL, data),
|
||||
deleteTool: (data: any): Promise<any> =>
|
||||
apiClient.post(endpoints.USER.DELETE_TOOL, data),
|
||||
};
|
||||
|
||||
export default userService;
|
||||
|
||||
15
frontend/src/assets/no-files-dark.svg
Normal file
15
frontend/src/assets/no-files-dark.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg width="113" height="124" viewBox="0 0 113 124" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="55.5" cy="71" r="53" fill="#2D2E33"/>
|
||||
<rect x="-0.599797" y="0.654564" width="43.9445" height="61.5222" rx="4.39444" transform="matrix(-0.999048 0.0436194 0.0436194 0.999048 68.9873 43.3176)" fill="#45464D" stroke="#5F6167" stroke-width="1.25556"/>
|
||||
<rect x="0.704349" y="-0.540466" width="46.4556" height="64.0333" rx="5.65" transform="matrix(-0.991445 -0.130526 -0.130526 0.991445 96.3673 40.893)" fill="#3F4147" stroke="#5F6167" stroke-width="1.25556"/>
|
||||
<path d="M94.3796 45.7849C94.7417 43.0349 92.8059 40.5122 90.0559 40.1501L55.2011 35.5614C52.4511 35.1994 49.9284 37.1352 49.5663 39.8851L48.3372 49.2212L93.1505 55.121L94.3796 45.7849Z" fill="#54555B"/>
|
||||
<rect width="1.88333" height="6.27778" rx="0.941667" transform="matrix(-0.130526 0.991445 0.991445 0.130526 40.4766 36.7888)" fill="#5F6167"/>
|
||||
<rect width="1.88333" height="6.27778" rx="0.941667" transform="matrix(-0.991445 -0.130526 -0.130526 0.991445 57.6758 26.3892)" fill="#5F6167"/>
|
||||
<rect width="1.88333" height="6.27778" rx="0.941667" transform="matrix(-0.793353 0.608761 0.608761 0.793353 46.6406 28.1023)" fill="#5F6167"/>
|
||||
<rect width="36.4111" height="2.51111" rx="1.25556" transform="matrix(-0.991445 -0.130526 -0.130526 0.991445 88.4668 57.0371)" fill="#5F6167"/>
|
||||
<rect width="36.4111" height="2.51111" rx="1.25556" transform="matrix(-0.991445 -0.130526 -0.130526 0.991445 86.8281 69.4851)" fill="#5F6167"/>
|
||||
<rect width="36.4111" height="2.51111" rx="1.25556" transform="matrix(-0.991445 -0.130526 -0.130526 0.991445 85.1895 81.9333)" fill="#5F6167"/>
|
||||
<rect width="13.1833" height="2.51111" rx="1.25556" transform="matrix(-0.991445 -0.130526 -0.130526 0.991445 87.8105 62.0164)" fill="#5F6167"/>
|
||||
<rect width="13.1833" height="2.51111" rx="1.25556" transform="matrix(-0.991445 -0.130526 -0.130526 0.991445 86.1719 74.4644)" fill="#5F6167"/>
|
||||
<rect width="13.1833" height="2.51111" rx="1.25556" transform="matrix(-0.991445 -0.130526 -0.130526 0.991445 84.5332 86.9126)" fill="#5F6167"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
15
frontend/src/assets/no-files.svg
Normal file
15
frontend/src/assets/no-files.svg
Normal file
@@ -0,0 +1,15 @@
|
||||
<svg width="113" height="124" viewBox="0 0 113 124" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<circle cx="55.5" cy="71" r="53" fill="#F1F1F1" fill-opacity="0.5"/>
|
||||
<rect x="-0.599797" y="0.654564" width="43.9445" height="61.5222" rx="4.39444" transform="matrix(-0.999048 0.0436194 0.0436194 0.999048 68.9873 43.3176)" fill="#EEEEEE" stroke="#999999" stroke-width="1.25556"/>
|
||||
<rect x="0.704349" y="-0.540466" width="46.4556" height="64.0333" rx="5.65" transform="matrix(-0.991445 -0.130526 -0.130526 0.991445 96.3673 40.893)" fill="#FAFAFA" stroke="#999999" stroke-width="1.25556"/>
|
||||
<path d="M94.3796 45.7849C94.7417 43.0349 92.8059 40.5122 90.0559 40.1501L55.2011 35.5614C52.4511 35.1994 49.9284 37.1352 49.5663 39.8851L48.3372 49.2212L93.1505 55.121L94.3796 45.7849Z" fill="#EEEEEE"/>
|
||||
<rect width="1.88333" height="6.27778" rx="0.941667" transform="matrix(-0.130526 0.991445 0.991445 0.130526 40.4766 36.7888)" fill="#999999"/>
|
||||
<rect width="1.88333" height="6.27778" rx="0.941667" transform="matrix(-0.991445 -0.130526 -0.130526 0.991445 57.6758 26.3892)" fill="#999999"/>
|
||||
<rect width="1.88333" height="6.27778" rx="0.941667" transform="matrix(-0.793353 0.608761 0.608761 0.793353 46.6406 28.1023)" fill="#999999"/>
|
||||
<rect width="36.4111" height="2.51111" rx="1.25556" transform="matrix(-0.991445 -0.130526 -0.130526 0.991445 88.4668 57.0371)" fill="#DCDCDC"/>
|
||||
<rect width="36.4111" height="2.51111" rx="1.25556" transform="matrix(-0.991445 -0.130526 -0.130526 0.991445 86.8281 69.4851)" fill="#DCDCDC"/>
|
||||
<rect width="36.4111" height="2.51111" rx="1.25556" transform="matrix(-0.991445 -0.130526 -0.130526 0.991445 85.1895 81.9333)" fill="#DCDCDC"/>
|
||||
<rect width="13.1833" height="2.51111" rx="1.25556" transform="matrix(-0.991445 -0.130526 -0.130526 0.991445 87.8105 62.0164)" fill="#EEEEEE"/>
|
||||
<rect width="13.1833" height="2.51111" rx="1.25556" transform="matrix(-0.991445 -0.130526 -0.130526 0.991445 86.1719 74.4644)" fill="#EEEEEE"/>
|
||||
<rect width="13.1833" height="2.51111" rx="1.25556" transform="matrix(-0.991445 -0.130526 -0.130526 0.991445 84.5332 86.9126)" fill="#EEEEEE"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -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<PaginationProps> = ({
|
||||
onPageChange,
|
||||
onRowsPerPageChange,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
const rowsPerPageOptions = [5, 10, 20, 50];
|
||||
|
||||
@@ -53,7 +55,9 @@ const Pagination: React.FC<PaginationProps> = ({
|
||||
<div className="flex items-center text-xs justify-end gap-4 mt-2 p-2 border-gray-200">
|
||||
{/* Rows per page dropdown */}
|
||||
<div className="flex items-center gap-2 relative">
|
||||
<span className="text-gray-900 dark:text-gray-50">Rows per page:</span>
|
||||
<span className="text-gray-900 dark:text-gray-50">
|
||||
{t('pagination.rowsPerPage')}:
|
||||
</span>
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={toggleDropdown}
|
||||
@@ -87,7 +91,7 @@ const Pagination: React.FC<PaginationProps> = ({
|
||||
|
||||
{/* Pagination controls */}
|
||||
<div className="text-gray-900 dark:text-gray-50">
|
||||
Page {currentPage} of {totalPages}
|
||||
{t('pagination.pageOf', { currentPage, totalPages })}
|
||||
</div>
|
||||
<div className="flex items-center gap-2 text-gray-900 dark:text-gray-50">
|
||||
<button
|
||||
@@ -97,7 +101,7 @@ const Pagination: React.FC<PaginationProps> = ({
|
||||
>
|
||||
<img
|
||||
src={DoubleArrowLeft}
|
||||
alt="First page"
|
||||
alt={t('pagination.firstPage')}
|
||||
className="dark:invert dark:sepia dark:brightness-200"
|
||||
/>
|
||||
</button>
|
||||
@@ -108,7 +112,7 @@ const Pagination: React.FC<PaginationProps> = ({
|
||||
>
|
||||
<img
|
||||
src={SingleArrowLeft}
|
||||
alt="Previous page"
|
||||
alt={t('pagination.previousPage')}
|
||||
className="dark:invert dark:sepia dark:brightness-200"
|
||||
/>
|
||||
</button>
|
||||
@@ -119,7 +123,7 @@ const Pagination: React.FC<PaginationProps> = ({
|
||||
>
|
||||
<img
|
||||
src={SingleArrowRight}
|
||||
alt="Next page"
|
||||
alt={t('pagination.nextPage')}
|
||||
className="dark:invert dark:sepia dark:brightness-200"
|
||||
/>
|
||||
</button>
|
||||
@@ -130,7 +134,7 @@ const Pagination: React.FC<PaginationProps> = ({
|
||||
>
|
||||
<img
|
||||
src={DoubleArrowRight}
|
||||
alt="Last page"
|
||||
alt={t('pagination.lastPage')}
|
||||
className="dark:invert dark:sepia dark:brightness-200"
|
||||
/>
|
||||
</button>
|
||||
|
||||
@@ -59,7 +59,8 @@ const SettingsBar = ({ setActiveTab, activeTab }: SettingsBarProps) => {
|
||||
<div className="md:hidden z-10">
|
||||
<button
|
||||
onClick={() => scrollTabs(-1)}
|
||||
className="flex h-6 w-6 items-center rounded-full justify-center transition-all hover:bg-gray-100"
|
||||
className="flex h-6 w-6 items-center rounded-full justify-center transition-all hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
aria-label="Scroll tabs left"
|
||||
>
|
||||
<img src={ArrowLeft} alt="left-arrow" className="h-3" />
|
||||
</button>
|
||||
@@ -67,16 +68,22 @@ const SettingsBar = ({ setActiveTab, activeTab }: SettingsBarProps) => {
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="flex flex-nowrap overflow-x-auto no-scrollbar md:space-x-4 scroll-smooth snap-x"
|
||||
role="tablist"
|
||||
aria-label="Settings tabs"
|
||||
>
|
||||
{tabs.map((tab, index) => (
|
||||
<button
|
||||
key={index}
|
||||
onClick={() => setActiveTab(tab)}
|
||||
className={`snap-start h-9 rounded-3xl px-4 font-bold hover:text-neutral-600 dark:hover:text-white/60 ${
|
||||
className={`snap-start h-9 rounded-3xl px-4 font-bold transition-colors ${
|
||||
activeTab === tab
|
||||
? 'bg-neutral-100 text-neutral-600 dark:bg-dark-charcoal dark:text-white/60'
|
||||
: 'text-gray-6000'
|
||||
? 'bg-neutral-200 text-neutral-900 dark:bg-dark-charcoal dark:text-white'
|
||||
: 'text-neutral-700 hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-white'
|
||||
}`}
|
||||
role="tab"
|
||||
aria-selected={activeTab === tab}
|
||||
aria-controls={`${tab.toLowerCase()}-panel`}
|
||||
id={`${tab.toLowerCase()}-tab`}
|
||||
>
|
||||
{tab}
|
||||
</button>
|
||||
@@ -85,7 +92,8 @@ const SettingsBar = ({ setActiveTab, activeTab }: SettingsBarProps) => {
|
||||
<div className="md:hidden z-10">
|
||||
<button
|
||||
onClick={() => scrollTabs(1)}
|
||||
className="flex h-6 w-6 rounded-full items-center justify-center hover:bg-gray-100"
|
||||
className="flex h-6 w-6 rounded-full items-center justify-center hover:bg-gray-200 dark:hover:bg-gray-700"
|
||||
aria-label="Scroll tabs right"
|
||||
>
|
||||
<img src={ArrowRight} alt="right-arrow" className="h-3" />
|
||||
</button>
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
<input {...getInputProps()}></input>
|
||||
<label htmlFor="file-upload" className="sr-only">
|
||||
{t('modals.uploadDoc.label')}
|
||||
</label>
|
||||
<input {...getInputProps()} id="file-upload" />
|
||||
<label htmlFor="message-input" className="sr-only">
|
||||
{t('inputPlaceholder')}
|
||||
</label>
|
||||
<textarea
|
||||
id="inputbox"
|
||||
id="message-input"
|
||||
ref={inputRef}
|
||||
tabIndex={1}
|
||||
placeholder={t('inputPlaceholder')}
|
||||
className={`inputbox-style w-full overflow-y-auto overflow-x-hidden whitespace-pre-wrap rounded-full bg-transparent py-5 text-base leading-tight opacity-100 focus:outline-none dark:bg-transparent dark:text-bright-gray`}
|
||||
className={`inputbox-style w-full overflow-y-auto overflow-x-hidden whitespace-pre-wrap rounded-full bg-transparent py-5 text-base leading-tight opacity-100 focus:outline-none dark:bg-transparent dark:text-bright-gray dark:placeholder-bright-gray dark:placeholder-opacity-50`}
|
||||
onInput={handleInput}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
@@ -400,19 +406,27 @@ export default function Conversation() {
|
||||
handleQuestionSubmission();
|
||||
}
|
||||
}}
|
||||
aria-label={t('inputPlaceholder')}
|
||||
></textarea>
|
||||
{status === 'loading' ? (
|
||||
<img
|
||||
src={isDarkTheme ? SpinnerDark : Spinner}
|
||||
className="relative right-[38px] bottom-[24px] -mr-[30px] animate-spin cursor-pointer self-end bg-transparent"
|
||||
></img>
|
||||
alt={t('loading')}
|
||||
/>
|
||||
) : (
|
||||
<div className="mx-1 cursor-pointer rounded-full p-3 text-center hover:bg-gray-3000 dark:hover:bg-dark-charcoal">
|
||||
<img
|
||||
className="ml-[4px] h-6 w-6 text-white "
|
||||
<button
|
||||
onClick={() => handleQuestionSubmission()}
|
||||
src={isDarkTheme ? SendDark : Send}
|
||||
></img>
|
||||
aria-label={t('send')}
|
||||
className="flex items-center justify-center"
|
||||
>
|
||||
<img
|
||||
className="ml-[4px] h-6 w-6 text-white"
|
||||
src={isDarkTheme ? SendDark : Send}
|
||||
alt={t('send')}
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -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<HTMLDivElement | null>(null);
|
||||
const chunks = useSelector(selectChunks);
|
||||
const selectedDocs = useSelector(selectSelectedDocs);
|
||||
@@ -113,13 +115,13 @@ const ConversationBubble = forwardRef<
|
||||
{isEditClicked && (
|
||||
<div ref={editableQueryRef} className="w-[75%] flex flex-col">
|
||||
<textarea
|
||||
placeholder="Type the updated query..."
|
||||
placeholder={t('conversation.edit.placeholder')}
|
||||
onChange={(e) => {
|
||||
setEditInputBox(e.target.value);
|
||||
}}
|
||||
rows={1}
|
||||
value={editInputBox}
|
||||
className="ml-2 mr-12 text-[15px] resize-y h-12 min-h-max rounded-3xl p-3 no-scrollbar leading-relaxed dark:border-[0.5px] dark:border-white dark:bg-raisin-black dark:text-white px-[18px] border-[1.5px] border-black"
|
||||
className="ml-2 mr-12 text-[15px] resize-y h-12 min-h-max rounded-3xl p-3 no-scrollbar leading-relaxed dark:border-[0.5px] dark:border-white dark:bg-raisin-black dark:text-white px-[18px] border-[1.5px] border-black"
|
||||
/>
|
||||
<div
|
||||
className={`flex flex-row-reverse justify-end gap-1 mt-3 text-sm font-medium`}
|
||||
@@ -128,13 +130,13 @@ const ConversationBubble = forwardRef<
|
||||
className="rounded-full bg-[#CDB5FF] hover:bg-[#E1D3FF] py-[10px] px-[15px] text-purple-30 max-w-full whitespace-pre-wrap leading-none"
|
||||
onClick={() => handleEditClick()}
|
||||
>
|
||||
Update
|
||||
{t('conversation.edit.update')}
|
||||
</button>
|
||||
<button
|
||||
className="py-[10px] px-[15px] no-underline hover:underline text-purple-30 max-w-full whitespace-pre-wrap leading-normal"
|
||||
onClick={() => setIsEditClicked(false)}
|
||||
>
|
||||
Cancel
|
||||
{t('conversation.edit.cancel')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -185,12 +187,14 @@ const ConversationBubble = forwardRef<
|
||||
avatar={
|
||||
<img
|
||||
src={Sources}
|
||||
alt="Sources"
|
||||
alt={t('conversation.sources.title')}
|
||||
className="h-full w-full object-fill"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<p className="text-base font-semibold">Sources</p>
|
||||
<p className="text-base font-semibold">
|
||||
{t('conversation.sources.title')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2 lg:grid-cols-4">
|
||||
{Array.from({ length: 4 }).map((_, index) => (
|
||||
@@ -217,12 +221,14 @@ const ConversationBubble = forwardRef<
|
||||
avatar={
|
||||
<img
|
||||
src={Sources}
|
||||
alt="Sources"
|
||||
alt={t('conversation.sources.title')}
|
||||
className="h-full w-full object-fill"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<p className="text-base font-semibold">Sources</p>
|
||||
<p className="text-base font-semibold">
|
||||
{t('conversation.sources.title')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="fade-in ml-3 mr-5 max-w-[90vw] md:max-w-[70vw] lg:max-w-[50vw]">
|
||||
<div className="grid grid-cols-2 gap-2 lg:grid-cols-4">
|
||||
@@ -289,9 +295,11 @@ const ConversationBubble = forwardRef<
|
||||
className="flex h-28 cursor-pointer flex-col-reverse rounded-[20px] bg-gray-1000 p-4 text-purple-30 hover:bg-[#F1F1F1] hover:text-[#6D3ECC] dark:bg-gun-metal dark:hover:bg-[#2C2E3C] dark:hover:text-[#8C67D7]"
|
||||
onClick={() => setIsSidebarOpen(true)}
|
||||
>
|
||||
<p className="ellipsis-text h-22 text-xs">{`View ${
|
||||
sources?.length ? sources.length - 3 : 0
|
||||
} more`}</p>
|
||||
<p className="ellipsis-text h-22 text-xs">
|
||||
{t('conversation.sources.view_more', {
|
||||
count: sources?.length ? sources.length - 3 : 0,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -306,12 +314,14 @@ const ConversationBubble = forwardRef<
|
||||
avatar={
|
||||
<img
|
||||
src={DocsGPT3}
|
||||
alt="DocsGPT"
|
||||
alt={t('conversation.answer')}
|
||||
className="h-full w-full object-cover"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<p className="text-base font-semibold">Answer</p>
|
||||
<p className="text-base font-semibold">
|
||||
{t('conversation.answer')}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className={`fade-in-bubble ml-2 mr-5 flex max-w-[90vw] rounded-[28px] bg-gray-1000 py-[14px] px-7 dark:bg-gun-metal md:max-w-[70vw] lg:max-w-[50vw] ${
|
||||
@@ -419,7 +429,7 @@ const ConversationBubble = forwardRef<
|
||||
${type !== 'ERROR' ? 'group-hover:lg:visible' : 'hidden'}`}
|
||||
>
|
||||
<div>
|
||||
<SpeakButton text={message} /> {/* Add SpeakButton here */}
|
||||
<SpeakButton text={message} />
|
||||
</div>
|
||||
</div>
|
||||
{type === 'ERROR' && (
|
||||
@@ -557,7 +567,7 @@ function AllSources(sources: AllSourcesProps) {
|
||||
{source.source && source.source !== 'local' ? (
|
||||
<img
|
||||
src={Link}
|
||||
alt="Link"
|
||||
alt={'Link'}
|
||||
className="h-3 w-3 cursor-pointer object-fill"
|
||||
onClick={() =>
|
||||
window.open(source.source, '_blank', 'noopener, noreferrer')
|
||||
|
||||
@@ -115,6 +115,19 @@ export default function ConversationTile({
|
||||
setConversationsName(conversation.name);
|
||||
setIsEdit(false);
|
||||
}
|
||||
|
||||
const handleRenameKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'Enter') {
|
||||
handleSaveConversation({
|
||||
id: conversation.id,
|
||||
name: conversationName,
|
||||
});
|
||||
} else if (e.key === 'Escape') {
|
||||
onClear();
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
@@ -144,6 +157,7 @@ export default function ConversationTile({
|
||||
className="h-6 w-full bg-transparent px-1 text-sm font-normal leading-6 focus:outline-[#0075FF]"
|
||||
value={conversationName}
|
||||
onChange={(e) => setConversationsName(e.target.value)}
|
||||
onKeyDown={handleRenameKeyDown}
|
||||
/>
|
||||
) : (
|
||||
<p className="my-auto overflow-hidden overflow-ellipsis whitespace-nowrap text-sm font-normal leading-6 text-eerie-black dark:text-white">
|
||||
@@ -239,7 +253,7 @@ export default function ConversationTile({
|
||||
>
|
||||
<img
|
||||
src={Trash}
|
||||
alt="Edit"
|
||||
alt="Delete"
|
||||
width={24}
|
||||
height={24}
|
||||
className="cursor-pointer hover:opacity-50"
|
||||
|
||||
@@ -236,7 +236,7 @@ export const SharedConversation = () => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className=" flex w-11/12 flex-col items-center gap-4 pb-2 md:w-10/12 lg:w-6/12">
|
||||
<div className="flex w-11/12 flex-col items-center gap-4 pb-2 md:w-10/12 lg:w-6/12">
|
||||
{apiKey ? (
|
||||
<div className="flex h-full w-full items-center rounded-[40px] border border-silver bg-white py-1 dark:bg-raisin-black">
|
||||
<div
|
||||
@@ -272,7 +272,7 @@ export const SharedConversation = () => {
|
||||
) : (
|
||||
<button
|
||||
onClick={() => navigate('/')}
|
||||
className="w-fit rounded-full bg-purple-30 p-4 text-white shadow-xl transition-colors duration-200 hover:bg-purple-taupe"
|
||||
className="w-fit rounded-full bg-purple-30 p-4 text-white shadow-xl transition-colors duration-200 hover:bg-purple-taupe mb-14 sm:mb-0"
|
||||
>
|
||||
{t('sharedConv.button')}
|
||||
</button>
|
||||
|
||||
@@ -41,16 +41,16 @@
|
||||
"selectLanguage": "Select Language",
|
||||
"chunks": "Chunks processed per query",
|
||||
"prompt": "Active Prompt",
|
||||
"deleteAllLabel": "Delete all Conversation",
|
||||
"deleteAllBtn": "Delete all",
|
||||
"deleteAllLabel": "Delete All Conversations",
|
||||
"deleteAllBtn": "Delete All",
|
||||
"addNew": "Add New",
|
||||
"convHistory": "Conversational history",
|
||||
"convHistory": "Conversation History",
|
||||
"none": "None",
|
||||
"low": "Low",
|
||||
"medium": "Medium",
|
||||
"high": "High",
|
||||
"unlimited": "Unlimited",
|
||||
"default": "default"
|
||||
"default": "Default"
|
||||
},
|
||||
"documents": {
|
||||
"label": "Documents",
|
||||
@@ -58,7 +58,19 @@
|
||||
"date": "Vector Date",
|
||||
"type": "Type",
|
||||
"tokenUsage": "Token Usage",
|
||||
"noData": "No existing Documents"
|
||||
"noData": "No existing Documents",
|
||||
"searchPlaceholder": "Search...",
|
||||
"addNew": "Add New",
|
||||
"preLoaded": "Pre-loaded",
|
||||
"private": "Private",
|
||||
"sync": "Sync",
|
||||
"syncFrequency": {
|
||||
"never": "Never",
|
||||
"daily": "Daily",
|
||||
"weekly": "Weekly",
|
||||
"monthly": "Monthly"
|
||||
},
|
||||
"actions": "Actions"
|
||||
},
|
||||
"apiKeys": {
|
||||
"label": "Chatbots",
|
||||
@@ -69,13 +81,40 @@
|
||||
"noData": "No existing Chatbots"
|
||||
},
|
||||
"analytics": {
|
||||
"label": "Analytics"
|
||||
"label": "Analytics",
|
||||
"filterByChatbot": "Filter by chatbot",
|
||||
"selectChatbot": "Select chatbot",
|
||||
"filterOptions": {
|
||||
"hour": "Hour",
|
||||
"last24Hours": "24 Hours",
|
||||
"last7Days": "7 Days",
|
||||
"last15Days": "15 Days",
|
||||
"last30Days": "30 Days"
|
||||
},
|
||||
"messages": "Messages",
|
||||
"tokenUsage": "Token Usage",
|
||||
"feedback": "Feedback",
|
||||
"filterPlaceholder": "Filter",
|
||||
"none": "None",
|
||||
"positiveFeedback": "Positive Feedback",
|
||||
"negativeFeedback": "Negative Feedback"
|
||||
},
|
||||
"logs": {
|
||||
"label": "Logs"
|
||||
"label": "Logs",
|
||||
"filterByChatbot": "Filter by chatbot",
|
||||
"selectChatbot": "Select chatbot",
|
||||
"none": "None",
|
||||
"tableHeader": "API generated / chatbot conversations"
|
||||
},
|
||||
"tools": {
|
||||
"label": "Tools"
|
||||
"label": "Tools",
|
||||
"searchPlaceholder": "Search tools...",
|
||||
"addTool": "Add Tool",
|
||||
"noToolsFound": "No tools found",
|
||||
"selectToolSetup": "Select a tool to set up",
|
||||
"settingsIconAlt": "Settings icon",
|
||||
"configureToolAria": "Configure {toolName}",
|
||||
"toggleToolAria": "Toggle {toolName}"
|
||||
}
|
||||
},
|
||||
"modals": {
|
||||
@@ -101,11 +140,19 @@
|
||||
"secret": "Client Secret",
|
||||
"agent": "User agent",
|
||||
"searchQueries": "Search queries",
|
||||
"numberOfPosts": "Number of posts"
|
||||
"numberOfPosts": "Number of posts",
|
||||
"addQuery": "Add Query"
|
||||
},
|
||||
"drag": {
|
||||
"title": "Upload a source file",
|
||||
"description": "Drop your file here to add it as a source"
|
||||
},
|
||||
"progress": {
|
||||
"upload": "Upload is in progress",
|
||||
"training": "Training is in progress",
|
||||
"completed": "Training completed",
|
||||
"wait": "This may take several minutes",
|
||||
"tokenLimit": "Over the token limit, please consider uploading smaller document"
|
||||
}
|
||||
},
|
||||
"createAPIKey": {
|
||||
@@ -132,6 +179,24 @@
|
||||
"note": "Source document, personal information and further conversation will remain private",
|
||||
"create": "Create",
|
||||
"option": "Allow users to prompt further"
|
||||
},
|
||||
"configTool": {
|
||||
"title": "Tool Config",
|
||||
"type": "Type",
|
||||
"apiKeyLabel": "API Key / OAuth",
|
||||
"apiKeyPlaceholder": "Enter API Key / OAuth",
|
||||
"addButton": "Add Tool",
|
||||
"closeButton": "Close"
|
||||
},
|
||||
"prompts": {
|
||||
"addPrompt": "Add Prompt",
|
||||
"addDescription": "Add your custom prompt and save it to DocsGPT",
|
||||
"editPrompt": "Edit Prompt",
|
||||
"editDescription": "Edit your custom prompt and save it to DocsGPT",
|
||||
"promptName": "Prompt Name",
|
||||
"promptText": "Prompt Text",
|
||||
"save": "Save",
|
||||
"nameExists": "Name already exists"
|
||||
}
|
||||
},
|
||||
"sharedConv": {
|
||||
@@ -144,5 +209,31 @@
|
||||
"delete": "Delete",
|
||||
"rename": "Rename",
|
||||
"deleteWarning": "Are you sure you want to delete this conversation?"
|
||||
},
|
||||
"pagination": {
|
||||
"rowsPerPage": "Rows per page",
|
||||
"pageOf": "Page {{currentPage}} of {{totalPages}}",
|
||||
"firstPage": "First page",
|
||||
"previousPage": "Previous page",
|
||||
"nextPage": "Next page",
|
||||
"lastPage": "Last page"
|
||||
},
|
||||
"conversation": {
|
||||
"copy": "Copy",
|
||||
"copied": "Copied",
|
||||
"speak": "Speak",
|
||||
"answer": "Answer",
|
||||
"edit": {
|
||||
"update": "Update",
|
||||
"cancel": "Cancel",
|
||||
"placeholder": "Type the updated query..."
|
||||
},
|
||||
"sources": {
|
||||
"title": "Sources",
|
||||
"text": "Source text",
|
||||
"link": "Source link",
|
||||
"view_more": "{{count}} more sources"
|
||||
},
|
||||
"retry": "Retry"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"language": "Spanish",
|
||||
"language": "Español",
|
||||
"chat": "Chat",
|
||||
"chats": "Chats",
|
||||
"newChat": "Nuevo Chat",
|
||||
@@ -8,7 +8,7 @@
|
||||
"inputPlaceholder": "Escribe tu mensaje aquí...",
|
||||
"tagline": "DocsGPT utiliza GenAI, por favor revisa información crítica utilizando fuentes.",
|
||||
"sourceDocs": "Fuente",
|
||||
"none": "Nada",
|
||||
"none": "Ninguno",
|
||||
"cancel": "Cancelar",
|
||||
"help": "Asistencia",
|
||||
"emailUs": "Envíanos un correo",
|
||||
@@ -36,29 +36,41 @@
|
||||
"general": {
|
||||
"label": "General",
|
||||
"selectTheme": "Seleccionar Tema",
|
||||
"light": "de luz",
|
||||
"dark": "oscura",
|
||||
"light": "Claro",
|
||||
"dark": "Oscuro",
|
||||
"selectLanguage": "Seleccionar Idioma",
|
||||
"chunks": "Trozos procesados por consulta",
|
||||
"chunks": "Fragmentos procesados por consulta",
|
||||
"prompt": "Prompt Activo",
|
||||
"deleteAllLabel": "Eliminar toda la Conversación",
|
||||
"deleteAllLabel": "Eliminar todas las conversaciones",
|
||||
"deleteAllBtn": "Eliminar todo",
|
||||
"addNew": "Agregar Nuevo",
|
||||
"convHistory": "Historia conversacional",
|
||||
"none": "ninguno",
|
||||
"convHistory": "Historial de conversaciones",
|
||||
"none": "Ninguno",
|
||||
"low": "Bajo",
|
||||
"medium": "Medio",
|
||||
"high": "Alto",
|
||||
"unlimited": "Ilimitado",
|
||||
"default": "predeterminada"
|
||||
"default": "Predeterminado"
|
||||
},
|
||||
"documents": {
|
||||
"label": "Documentos",
|
||||
"name": "Nombre del Documento",
|
||||
"date": "Fecha Vector",
|
||||
"date": "Fecha de Vector",
|
||||
"type": "Tipo",
|
||||
"tokenUsage": "Uso de Tokens",
|
||||
"noData": "No hay documentos existentes"
|
||||
"noData": "No hay documentos existentes",
|
||||
"searchPlaceholder": "Buscar...",
|
||||
"addNew": "Agregar Nuevo",
|
||||
"preLoaded": "Precargado",
|
||||
"private": "Privado",
|
||||
"sync": "Sincronizar",
|
||||
"syncFrequency": {
|
||||
"never": "Nunca",
|
||||
"daily": "Diario",
|
||||
"weekly": "Semanal",
|
||||
"monthly": "Mensual"
|
||||
},
|
||||
"actions": "Acciones"
|
||||
},
|
||||
"apiKeys": {
|
||||
"label": "Chatbots",
|
||||
@@ -69,24 +81,54 @@
|
||||
"noData": "No hay chatbots existentes"
|
||||
},
|
||||
"analytics": {
|
||||
"label": "Analítica"
|
||||
"label": "Analítica",
|
||||
"filterByChatbot": "Filtrar por chatbot",
|
||||
"selectChatbot": "Seleccionar chatbot",
|
||||
"filterOptions": {
|
||||
"hour": "Hora",
|
||||
"last24Hours": "24 Horas",
|
||||
"last7Days": "7 Días",
|
||||
"last15Days": "15 Días",
|
||||
"last30Days": "30 Días"
|
||||
},
|
||||
"messages": "Mensajes",
|
||||
"tokenUsage": "Uso de Tokens",
|
||||
"feedback": "Retroalimentación",
|
||||
"filterPlaceholder": "Filtrar",
|
||||
"none": "Ninguno",
|
||||
"positiveFeedback": "Retroalimentación Positiva",
|
||||
"negativeFeedback": "Retroalimentación Negativa"
|
||||
},
|
||||
"logs": {
|
||||
"label": "Registros"
|
||||
"label": "Registros",
|
||||
"filterByChatbot": "Filtrar por chatbot",
|
||||
"selectChatbot": "Seleccionar chatbot",
|
||||
"none": "Ninguno",
|
||||
"tableHeader": "Conversaciones generadas por API / chatbot"
|
||||
},
|
||||
"tools": {
|
||||
"label": "Herramientas",
|
||||
"searchPlaceholder": "Buscar...",
|
||||
"addTool": "Agregar Herramienta",
|
||||
"noToolsFound": "No se encontraron herramientas",
|
||||
"selectToolSetup": "Seleccione una herramienta para configurar",
|
||||
"settingsIconAlt": "Icono de configuración",
|
||||
"configureToolAria": "Configurar {toolName}",
|
||||
"toggleToolAria": "Alternar {toolName}"
|
||||
}
|
||||
},
|
||||
"modals": {
|
||||
"uploadDoc": {
|
||||
"label": "Subir nuevo documento",
|
||||
"select": "Elija cómo cargar su documento en DocsGPT",
|
||||
"select": "Elige cómo cargar tu documento en DocsGPT",
|
||||
"file": "Subir desde el dispositivo",
|
||||
"back": "Atrás",
|
||||
"wait": "Espere por favor ...",
|
||||
"wait": "Por favor espera ...",
|
||||
"remote": "Recoger desde un sitio web",
|
||||
"start": "Empezar a chatear",
|
||||
"start": "Comenzar a chatear",
|
||||
"name": "Nombre",
|
||||
"choose": "Seleccionar Archivos",
|
||||
"info": "Por favor, suba archivos .pdf, .txt, .rst, .csv, .xlsx, .docx, .md, .html, .epub, .json, .pptx, .zip limitados a 25 MB",
|
||||
"info": "Por favor, sube archivos .pdf, .txt, .rst, .csv, .xlsx, .docx, .md, .html, .epub, .json, .pptx, .zip limitados a 25MB",
|
||||
"uploadedFiles": "Archivos Subidos",
|
||||
"cancel": "Cancelar",
|
||||
"train": "Entrenar",
|
||||
@@ -94,52 +136,104 @@
|
||||
"urlLink": "Enlace URL",
|
||||
"repoUrl": "URL del Repositorio",
|
||||
"reddit": {
|
||||
"id": "ID de Cliente",
|
||||
"secret": "Secreto de Cliente",
|
||||
"agent": "Agente de Usuario",
|
||||
"searchQueries": "Consultas de Búsqueda",
|
||||
"numberOfPosts": "Número de publicaciones"
|
||||
"id": "ID del Cliente",
|
||||
"secret": "Secreto del Cliente",
|
||||
"agent": "Agente de usuario",
|
||||
"searchQueries": "Consultas de búsqueda",
|
||||
"numberOfPosts": "Número de publicaciones",
|
||||
"addQuery": "Agregar Consulta"
|
||||
},
|
||||
"drag": {
|
||||
"title": "Cargar un archivo fuente",
|
||||
"description": "Suelta tu archivo aquí para agregarlo como fuente."
|
||||
"title": "Subir archivo fuente",
|
||||
"description": "Arrastra tu archivo aquí para agregarlo como fuente"
|
||||
},
|
||||
"progress": {
|
||||
"upload": "Subida en progreso",
|
||||
"training": "Entrenamiento en progreso",
|
||||
"completed": "Entrenamiento completado",
|
||||
"wait": "Esto puede tardar varios minutos",
|
||||
"tokenLimit": "Excede el límite de tokens, considere cargar un documento más pequeño"
|
||||
}
|
||||
},
|
||||
"createAPIKey": {
|
||||
"label": "Crear Nueva Clave de API",
|
||||
"apiKeyName": "Nombre de la Clave de API",
|
||||
"chunks": "Fragmentos procesados por consulta",
|
||||
"prompt": "Seleccione el prompt activo",
|
||||
"prompt": "Selecciona el prompt activo",
|
||||
"sourceDoc": "Documento Fuente",
|
||||
"create": "Crear"
|
||||
},
|
||||
"saveKey": {
|
||||
"note": "Por favor, guarde su Clave",
|
||||
"disclaimer": "Esta es la única vez que se mostrará su clave.",
|
||||
"note": "Por favor, guarda tu Clave",
|
||||
"disclaimer": "Esta es la única vez que se mostrará tu clave.",
|
||||
"copy": "Copiar",
|
||||
"copied": "Copiado",
|
||||
"confirm": "He guardado la Clave"
|
||||
},
|
||||
"deleteConv": {
|
||||
"confirm": "¿Está seguro de que desea eliminar todas las conversaciones?",
|
||||
"confirm": "¿Estás seguro de que deseas eliminar todas las conversaciones?",
|
||||
"delete": "Eliminar"
|
||||
},
|
||||
"shareConv": {
|
||||
"label": "Crear una página pública para compartir",
|
||||
"note": "El documento original, la información personal y las conversaciones posteriores permanecerán privadas",
|
||||
"note": "El documento fuente, información personal y conversaciones posteriores permanecerán privadas",
|
||||
"create": "Crear",
|
||||
"option": "Permitir a los usuarios realizar más consultas."
|
||||
"option": "Permitir a los usuarios realizar más consultas"
|
||||
},
|
||||
"configTool": {
|
||||
"title": "Configuración de la Herramienta",
|
||||
"type": "Tipo",
|
||||
"apiKeyLabel": "Clave API / OAuth",
|
||||
"apiKeyPlaceholder": "Ingrese la Clave API / OAuth",
|
||||
"addButton": "Agregar Herramienta",
|
||||
"closeButton": "Cerrar"
|
||||
},
|
||||
"prompts": {
|
||||
"addPrompt": "Agregar Prompt",
|
||||
"addDescription": "Agrega tu prompt personalizado y guárdalo en DocsGPT",
|
||||
"editPrompt": "Editar Prompt",
|
||||
"editDescription": "Edita tu prompt personalizado y guárdalo en DocsGPT",
|
||||
"promptName": "Nombre del Prompt",
|
||||
"promptText": "Texto del Prompt",
|
||||
"save": "Guardar",
|
||||
"nameExists": "El nombre ya existe"
|
||||
}
|
||||
},
|
||||
"sharedConv": {
|
||||
"subtitle": "Creado con",
|
||||
"button": "Comienza con DocsGPT",
|
||||
"meta": "DocsGPT utiliza GenAI, por favor revise la información crítica utilizando fuentes."
|
||||
"meta": "DocsGPT utiliza GenAI, por favor revisa la información crítica utilizando fuentes."
|
||||
},
|
||||
"convTile": {
|
||||
"share": "Compartir",
|
||||
"delete": "Eliminar",
|
||||
"rename": "Renombrar",
|
||||
"deleteWarning": "¿Está seguro de que desea eliminar esta conversación?"
|
||||
},
|
||||
"pagination": {
|
||||
"rowsPerPage": "Filas por página",
|
||||
"pageOf": "Página {{currentPage}} de {{totalPages}}",
|
||||
"firstPage": "Primera página",
|
||||
"previousPage": "Página anterior",
|
||||
"nextPage": "Página siguiente",
|
||||
"lastPage": "Última página"
|
||||
},
|
||||
"conversation": {
|
||||
"copy": "Copiar",
|
||||
"copied": "Copiado",
|
||||
"speak": "Hablar",
|
||||
"answer": "Respuesta",
|
||||
"edit": {
|
||||
"update": "Actualizar",
|
||||
"cancel": "Cancelar",
|
||||
"placeholder": "Ingrese la consulta actualizada..."
|
||||
},
|
||||
"sources": {
|
||||
"title": "Fuentes",
|
||||
"text": "Texto fuente",
|
||||
"link": "Enlace fuente",
|
||||
"view_more": "Ver {{count}} más fuentes"
|
||||
},
|
||||
"retry": "Reintentar"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
},
|
||||
{
|
||||
"header": "学習支援",
|
||||
"query": "コンテキストに対する潜在的な質問を書いてください"
|
||||
"query": "このコンテンツに対する可能な質問を書いてください"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
@@ -58,21 +58,63 @@
|
||||
"date": "ベクトル日付",
|
||||
"type": "タイプ",
|
||||
"tokenUsage": "トークン使用量",
|
||||
"noData": "既存のドキュメントはありません"
|
||||
"noData": "既存のドキュメントがありません",
|
||||
"searchPlaceholder": "検索...",
|
||||
"addNew": "新規追加",
|
||||
"preLoaded": "プリロード済み",
|
||||
"private": "プライベート",
|
||||
"sync": "同期",
|
||||
"syncFrequency": {
|
||||
"never": "なし",
|
||||
"daily": "毎日",
|
||||
"weekly": "毎週",
|
||||
"monthly": "毎月"
|
||||
},
|
||||
"actions": "アクション"
|
||||
},
|
||||
"apiKeys": {
|
||||
"label": "チャットボット",
|
||||
"label": "APIキー",
|
||||
"name": "名前",
|
||||
"key": "APIキー",
|
||||
"sourceDoc": "ソースドキュメント",
|
||||
"createNew": "新規作成",
|
||||
"noData": "既存のチャットボットはありません"
|
||||
"noData": "既存のAPIキーがありません"
|
||||
},
|
||||
"analytics": {
|
||||
"label": "分析"
|
||||
"label": "分析",
|
||||
"filterByChatbot": "チャットボットでフィルター",
|
||||
"selectChatbot": "チャットボットを選択",
|
||||
"filterOptions": {
|
||||
"hour": "時間",
|
||||
"last24Hours": "過去24時間",
|
||||
"last7Days": "過去7日間",
|
||||
"last15Days": "過去15日間",
|
||||
"last30Days": "過去30日間"
|
||||
},
|
||||
"messages": "メッセージ",
|
||||
"tokenUsage": "トークン使用量",
|
||||
"feedback": "フィードバック",
|
||||
"filterPlaceholder": "フィルター",
|
||||
"none": "なし",
|
||||
"positiveFeedback": "肯定的なフィードバック",
|
||||
"negativeFeedback": "否定的なフィードバック"
|
||||
},
|
||||
"logs": {
|
||||
"label": "ログ"
|
||||
"label": "ログ",
|
||||
"filterByChatbot": "チャットボットでフィルター",
|
||||
"selectChatbot": "チャットボットを選択",
|
||||
"none": "なし",
|
||||
"tableHeader": "API生成 / チャットボットの会話"
|
||||
},
|
||||
"tools": {
|
||||
"label": "ツール",
|
||||
"searchPlaceholder": "検索...",
|
||||
"addTool": "ツールを追加",
|
||||
"noToolsFound": "ツールが見つかりません",
|
||||
"selectToolSetup": "設定するツールを選択してください",
|
||||
"settingsIconAlt": "設定アイコン",
|
||||
"configureToolAria": "{toolName} を設定",
|
||||
"toggleToolAria": "{toolName} を切り替え"
|
||||
}
|
||||
},
|
||||
"modals": {
|
||||
@@ -86,7 +128,7 @@
|
||||
"start": "チャットを開始する",
|
||||
"name": "名前",
|
||||
"choose": "ファイルを選択",
|
||||
"info": ".pdf, .txt, .rst, .docx, .md, .json, .pptx, .zipファイルを25MBまでアップロードしてください",
|
||||
"info": "25MBまでの.pdf、.txt、.rst、.csv、.xlsx、.docx、.md、.html、.epub、.json、.pptx、.zipファイルをアップロードしてください",
|
||||
"uploadedFiles": "アップロードされたファイル",
|
||||
"cancel": "キャンセル",
|
||||
"train": "トレーニング",
|
||||
@@ -98,11 +140,19 @@
|
||||
"secret": "クライアントシークレット",
|
||||
"agent": "ユーザーエージェント",
|
||||
"searchQueries": "検索クエリ",
|
||||
"numberOfPosts": "投稿数"
|
||||
"numberOfPosts": "投稿数",
|
||||
"addQuery": "クエリを追加"
|
||||
},
|
||||
"drag": {
|
||||
"title": "ソースファイルをアップロードする",
|
||||
"description": "ファイルをここにドロップしてソースとして追加します"
|
||||
"title": "ソースファイルをアップロード",
|
||||
"description": "ファイルをここにドロップしてソースとして追加してください"
|
||||
},
|
||||
"progress": {
|
||||
"upload": "アップロード中",
|
||||
"training": "トレーニング中",
|
||||
"completed": "トレーニング完了",
|
||||
"wait": "数分かかる場合があります",
|
||||
"tokenLimit": "トークン制限を超えています。より小さいドキュメントをアップロードしてください"
|
||||
}
|
||||
},
|
||||
"createAPIKey": {
|
||||
@@ -129,6 +179,24 @@
|
||||
"note": "ソースドキュメント、個人情報、および以降の会話は非公開のままになります",
|
||||
"create": "作成",
|
||||
"option": "ユーザーがより多くのクエリを実行できるようにします。"
|
||||
},
|
||||
"configTool": {
|
||||
"title": "ツール設定",
|
||||
"type": "タイプ",
|
||||
"apiKeyLabel": "APIキー / OAuth",
|
||||
"apiKeyPlaceholder": "APIキー / OAuthを入力してください",
|
||||
"addButton": "ツールを追加",
|
||||
"closeButton": "閉じる"
|
||||
},
|
||||
"prompts": {
|
||||
"addPrompt": "プロンプトを追加",
|
||||
"addDescription": "カスタムプロンプトを追加してDocsGPTに保存",
|
||||
"editPrompt": "プロンプトを編集",
|
||||
"editDescription": "カスタムプロンプトを編集してDocsGPTに保存",
|
||||
"promptName": "プロンプト名",
|
||||
"promptText": "プロンプトテキスト",
|
||||
"save": "保存",
|
||||
"nameExists": "名前が既に存在します"
|
||||
}
|
||||
},
|
||||
"sharedConv": {
|
||||
@@ -141,5 +209,31 @@
|
||||
"delete": "削除",
|
||||
"rename": "名前変更",
|
||||
"deleteWarning": "この会話を削除してもよろしいですか?"
|
||||
},
|
||||
"pagination": {
|
||||
"rowsPerPage": "1ページあたりの行数",
|
||||
"pageOf": "ページ {{currentPage}} / {{totalPages}}",
|
||||
"firstPage": "最初のページ",
|
||||
"previousPage": "前のページ",
|
||||
"nextPage": "次のページ",
|
||||
"lastPage": "最後のページ"
|
||||
},
|
||||
"conversation": {
|
||||
"copy": "コピー",
|
||||
"copied": "コピー済み",
|
||||
"speak": "読み上げ",
|
||||
"answer": "回答",
|
||||
"edit": {
|
||||
"update": "更新",
|
||||
"cancel": "キャンセル",
|
||||
"placeholder": "更新されたクエリを入力..."
|
||||
},
|
||||
"sources": {
|
||||
"title": "ソース",
|
||||
"text": "ソーステキスト",
|
||||
"link": "ソースリンク",
|
||||
"view_more": "さらに{{count}}個のソースを表示"
|
||||
},
|
||||
"retry": "再試行"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,142 +1,239 @@
|
||||
{
|
||||
"language": "Русский",
|
||||
"chat": "Чат",
|
||||
"chats": "Чаты",
|
||||
"newChat": "Новый чат",
|
||||
"myPlan": "Мой план",
|
||||
"about": "О",
|
||||
"inputPlaceholder": "Введите свое сообщение здесь...",
|
||||
"tagline": "DocsGPT использует GenAI, пожалуйста, проверьте важную информацию, используя источники.",
|
||||
"sourceDocs": "Источник",
|
||||
"none": "Нет",
|
||||
"cancel": "Отмена",
|
||||
"demo": [
|
||||
{
|
||||
"header": "Узнайте о DocsGPT",
|
||||
"query": "Что такое DocsGPT?"
|
||||
},
|
||||
{
|
||||
"header": "Обобщить документацию",
|
||||
"query": "Обобщить текущий контекст"
|
||||
},
|
||||
{
|
||||
"header": "Написать код",
|
||||
"query": "Написать код для запроса API к /api/answer"
|
||||
},
|
||||
{
|
||||
"header": "Помощь в обучении",
|
||||
"query": "Написать потенциальные вопросы для контекста"
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"label": "Настройки",
|
||||
"general": {
|
||||
"label": "Общие",
|
||||
"selectTheme": "Выбрать тему",
|
||||
"light": "Светлая",
|
||||
"dark": "Темная",
|
||||
"selectLanguage": "Выбрать язык",
|
||||
"chunks": "Обработанные фрагменты на запрос",
|
||||
"prompt": "Активная подсказка",
|
||||
"deleteAllLabel": "Удалить все беседы",
|
||||
"deleteAllBtn": "Удалить все",
|
||||
"addNew": "Добавить новый",
|
||||
"convHistory": "История разговоров",
|
||||
"none": "Нет",
|
||||
"low": "Низкий",
|
||||
"medium": "Средний",
|
||||
"high": "Высокий",
|
||||
"unlimited": "Без ограничений",
|
||||
"default": "по умолчанию"
|
||||
},
|
||||
"documents": {
|
||||
"label": "Документы",
|
||||
"name": "Название документа",
|
||||
"date": "Дата вектора",
|
||||
"type": "Тип",
|
||||
"tokenUsage": "Использование токена",
|
||||
"noData": "Нет существующих документов"
|
||||
},
|
||||
"apiKeys": {
|
||||
"label": "Чат-боты",
|
||||
"name": "Название",
|
||||
"key": "Ключ API",
|
||||
"sourceDoc": "Исходный документ",
|
||||
"createNew": "Создать новый",
|
||||
"noData": "Нет существующих чат-ботов"
|
||||
},
|
||||
"analytics": {
|
||||
"label": "Analytics"
|
||||
},
|
||||
"logs": {
|
||||
"label": "Журналы"
|
||||
}
|
||||
"language": "Русский",
|
||||
"chat": "Чат",
|
||||
"chats": "Чаты",
|
||||
"newChat": "Новый чат",
|
||||
"myPlan": "Мой план",
|
||||
"about": "О нас",
|
||||
"inputPlaceholder": "Введите свое сообщение здесь...",
|
||||
"tagline": "DocsGPT использует GenAI, пожалуйста, проверьте важную информацию, используя источники.",
|
||||
"sourceDocs": "Источник",
|
||||
"none": "Нет",
|
||||
"cancel": "Отмена",
|
||||
"help": "Помощь",
|
||||
"emailUs": "Напишите нам",
|
||||
"documentation": "Документация",
|
||||
"demo": [
|
||||
{
|
||||
"header": "Узнайте о DocsGPT",
|
||||
"query": "Что такое DocsGPT?"
|
||||
},
|
||||
"modals": {
|
||||
"uploadDoc": {
|
||||
"label": "Загрузить новую документацию",
|
||||
"select": "Выберите способ загрузки документа в DocsGPT",
|
||||
"file": "Загрузить с устройства",
|
||||
"back": "Назад",
|
||||
"wait": "Пожалуйста, подождите ...",
|
||||
"remote": "Собрать с веб-сайта",
|
||||
"start": "Начать чат",
|
||||
"name": "Имя",
|
||||
"choose": "Выбрать файлы",
|
||||
"info": "Загрузите .pdf, .txt, .rst, .csv, .xlsx, .docx, .md, .zip с ограничением до 25 МБ",
|
||||
"uploadedFiles": "Загруженные файлы",
|
||||
"cancel": "Отмена",
|
||||
"train": "Обучение",
|
||||
"link": "Ссылка",
|
||||
"urlLink": "URL-ссылка",
|
||||
"repoUrl": "URL-адрес репозитория",
|
||||
"reddit": {
|
||||
"id": "ID клиента",
|
||||
"secret": "Секрет клиента",
|
||||
"agent": "Агент пользователя",
|
||||
"searchQueries": "Поисковые запросы",
|
||||
"numberOfPosts": "Количество сообщений"
|
||||
},
|
||||
"drag": {
|
||||
"title": "Загрузите исходный файл",
|
||||
"description": "Перетащите сюда свой файл, чтобы добавить его в качестве источника."
|
||||
}
|
||||
},
|
||||
"createAPIKey": {
|
||||
"label": "Создать новый ключ API",
|
||||
"apiKeyName": "Имя ключа API",
|
||||
"chunks": "Обработано фрагментов на запрос",
|
||||
"prompt": "Выбрать активный запрос",
|
||||
"sourceDoc": "Исходный документ",
|
||||
"create": "Создать"
|
||||
},
|
||||
"saveKey": {
|
||||
"note": "Пожалуйста, сохраните свой ключ",
|
||||
"disclaimer": "Это единственный раз, когда будет показан ваш ключ.",
|
||||
"copy": "Копировать",
|
||||
"copied": "Скопировано",
|
||||
"confirm": "Я сохранил ключ"
|
||||
},
|
||||
"deleteConv": {
|
||||
"confirm": "Вы уверены, что хотите удалить все разговоры?",
|
||||
"delete": "Удалить"
|
||||
},
|
||||
"shareConv": {
|
||||
"label": "Создать публичную страницу для общего доступа",
|
||||
"note": "Исходный документ, личная информация и дальнейший разговор останутся конфиденциальными",
|
||||
"create": "Создать",
|
||||
"option": "Разрешить пользователям запрашивать дальнейшие действия"
|
||||
}
|
||||
{
|
||||
"header": "Обобщить документацию",
|
||||
"query": "Обобщить текущий контекст"
|
||||
},
|
||||
"sharedConv": {
|
||||
"subtitle": "Создано с помощью",
|
||||
"button": "Начать работу с DocsGPT",
|
||||
"meta": "DocsGPT использует GenAI, пожалуйста, проверьте важную информацию с помощью источников."
|
||||
{
|
||||
"header": "Написать код",
|
||||
"query": "Написать код для запроса API к /api/answer"
|
||||
},
|
||||
"convTile": {
|
||||
"share": "Поделиться",
|
||||
"delete": "Удалить",
|
||||
"rename": "Переименовать",
|
||||
"deleteWarning": "Вы уверены, что хотите удалить этот разговор?"
|
||||
{
|
||||
"header": "Помощь в обучении",
|
||||
"query": "Написать возможные вопросы для этого контента"
|
||||
}
|
||||
}
|
||||
],
|
||||
"settings": {
|
||||
"label": "Настройки",
|
||||
"general": {
|
||||
"label": "Общие",
|
||||
"selectTheme": "Выбрать тему",
|
||||
"light": "Светлая",
|
||||
"dark": "Тёмная",
|
||||
"selectLanguage": "Выбрать язык",
|
||||
"chunks": "Обработанные фрагменты на запрос",
|
||||
"prompt": "Активная подсказка",
|
||||
"deleteAllLabel": "Удалить все беседы",
|
||||
"deleteAllBtn": "Удалить все",
|
||||
"addNew": "Добавить новый",
|
||||
"convHistory": "История разговоров",
|
||||
"none": "Нет",
|
||||
"low": "Низкий",
|
||||
"medium": "Средний",
|
||||
"high": "Высокий",
|
||||
"unlimited": "Без ограничений",
|
||||
"default": "По умолчанию"
|
||||
},
|
||||
"documents": {
|
||||
"label": "Документы",
|
||||
"name": "Название документа",
|
||||
"date": "Дата вектора",
|
||||
"type": "Тип",
|
||||
"tokenUsage": "Использование токена",
|
||||
"noData": "Нет существующих документов",
|
||||
"searchPlaceholder": "Поиск...",
|
||||
"addNew": "добавить новый",
|
||||
"preLoaded": "Предзагруженный",
|
||||
"private": "Частный",
|
||||
"sync": "Синхронизация",
|
||||
"syncFrequency": {
|
||||
"never": "Никогда",
|
||||
"daily": "Ежедневно",
|
||||
"weekly": "Еженедельно",
|
||||
"monthly": "Ежемесячно"
|
||||
},
|
||||
"actions": "Действия"
|
||||
},
|
||||
"apiKeys": {
|
||||
"label": "API ключи",
|
||||
"name": "Название",
|
||||
"key": "API ключ",
|
||||
"sourceDoc": "Источник документа",
|
||||
"createNew": "Создать новый",
|
||||
"noData": "Нет существующих API ключей"
|
||||
},
|
||||
"analytics": {
|
||||
"label": "Аналитика",
|
||||
"filterByChatbot": "Фильтровать по чат-боту",
|
||||
"selectChatbot": "Выбрать чат-бота",
|
||||
"filterOptions": {
|
||||
"hour": "Час",
|
||||
"last24Hours": "Последние 24 часа",
|
||||
"last7Days": "Последние 7 дней",
|
||||
"last15Days": "Последние 15 дней",
|
||||
"last30Days": "Последние 30 дней"
|
||||
},
|
||||
"messages": "Сообщения",
|
||||
"tokenUsage": "Использование токена",
|
||||
"feedback": "Обратная связь",
|
||||
"filterPlaceholder": "Фильтр",
|
||||
"none": "Нет",
|
||||
"positiveFeedback": "Положительная обратная связь",
|
||||
"negativeFeedback": "Отрицательная обратная связь"
|
||||
},
|
||||
"logs": {
|
||||
"label": "Журналы",
|
||||
"filterByChatbot": "Фильтровать по чат-боту",
|
||||
"selectChatbot": "Выбрать чат-бота",
|
||||
"none": "Нет",
|
||||
"tableHeader": "API сгенерировано / разговоры с чат-ботом"
|
||||
},
|
||||
"tools": {
|
||||
"label": "Инструменты",
|
||||
"searchPlaceholder": "Поиск...",
|
||||
"addTool": "Добавить инструмент",
|
||||
"noToolsFound": "Инструменты не найдены",
|
||||
"selectToolSetup": "Выберите инструмент для настройки",
|
||||
"settingsIconAlt": "Иконка настроек",
|
||||
"configureToolAria": "Настроить {toolName}",
|
||||
"toggleToolAria": "Переключить {toolName}"
|
||||
}
|
||||
},
|
||||
"modals": {
|
||||
"uploadDoc": {
|
||||
"label": "Загрузить новый документ",
|
||||
"select": "Выберите способ загрузки документа в DocsGPT",
|
||||
"file": "Загрузить с устройства",
|
||||
"back": "Назад",
|
||||
"wait": "Пожалуйста, подождите...",
|
||||
"remote": "Собрать с веб-сайта",
|
||||
"start": "Начать чат",
|
||||
"name": "Имя",
|
||||
"choose": "Выбрать файлы",
|
||||
"info": "Пожалуйста, загрузите файлы .pdf, .txt, .rst, .csv, .xlsx, .docx, .md, .html, .epub, .json, .pptx, .zip размером до 25 МБ",
|
||||
"uploadedFiles": "Загруженные файлы",
|
||||
"cancel": "Отмена",
|
||||
"train": "Тренировка",
|
||||
"link": "Ссылка",
|
||||
"urlLink": "URL ссылка",
|
||||
"repoUrl": "URL репозитория",
|
||||
"reddit": {
|
||||
"id": "ID клиента",
|
||||
"secret": "Секрет клиента",
|
||||
"agent": "Пользовательский агент",
|
||||
"searchQueries": "Поисковые запросы",
|
||||
"numberOfPosts": "Количество сообщений",
|
||||
"addQuery": "Добавить запрос"
|
||||
},
|
||||
"drag": {
|
||||
"title": "Загрузить исходный файл",
|
||||
"description": "Перетащите файл сюда, чтобы добавить его как источник"
|
||||
},
|
||||
"progress": {
|
||||
"upload": "Идет загрузка",
|
||||
"training": "Идет обучение",
|
||||
"completed": "Обучение завершено",
|
||||
"wait": "Это может занять несколько минут",
|
||||
"tokenLimit": "Превышен лимит токенов, рассмотрите возможность загрузки документа меньшего размера"
|
||||
}
|
||||
},
|
||||
"createAPIKey": {
|
||||
"label": "Создать новый API ключ",
|
||||
"apiKeyName": "Название API ключа",
|
||||
"chunks": "Обработанные фрагменты на запрос",
|
||||
"prompt": "Выбрать активную подсказку",
|
||||
"sourceDoc": "Источник документа",
|
||||
"create": "Создать"
|
||||
},
|
||||
"saveKey": {
|
||||
"note": "Пожалуйста, сохраните ваш ключ",
|
||||
"disclaimer": "Ваш ключ будет показан только один раз.",
|
||||
"copy": "Копировать",
|
||||
"copied": "Скопировано",
|
||||
"confirm": "Я сохранил ключ"
|
||||
},
|
||||
"deleteConv": {
|
||||
"confirm": "Вы уверены, что хотите удалить все разговоры?",
|
||||
"delete": "Удалить"
|
||||
},
|
||||
"shareConv": {
|
||||
"label": "Создать публичную страницу для совместного использования",
|
||||
"note": "Исходный документ, личная информация и последующие разговоры останутся приватными",
|
||||
"create": "Создать",
|
||||
"option": "Позволить пользователям делать дополнительные запросы."
|
||||
},
|
||||
"configTool": {
|
||||
"title": "Настройка инструмента",
|
||||
"type": "Тип",
|
||||
"apiKeyLabel": "API ключ / OAuth",
|
||||
"apiKeyPlaceholder": "Введите API ключ / OAuth",
|
||||
"addButton": "Добавить инструмент",
|
||||
"closeButton": "Закрыть"
|
||||
},
|
||||
"prompts": {
|
||||
"addPrompt": "Добавить подсказку",
|
||||
"addDescription": "Добавить вашу пользовательскую подсказку и сохранить её в DocsGPT",
|
||||
"editPrompt": "Редактировать подсказку",
|
||||
"editDescription": "Редактировать вашу пользовательскую подсказку и сохранить её в DocsGPT",
|
||||
"promptName": "Название подсказки",
|
||||
"promptText": "Текст подсказки",
|
||||
"save": "Сохранить",
|
||||
"nameExists": "Название уже существует"
|
||||
}
|
||||
},
|
||||
"sharedConv": {
|
||||
"subtitle": "Создано с помощью",
|
||||
"button": "Начать работу с DocsGPT",
|
||||
"meta": "DocsGPT использует GenAI, пожалуйста, проверьте важную информацию, используя источники."
|
||||
},
|
||||
"convTile": {
|
||||
"share": "Поделиться",
|
||||
"delete": "Удалить",
|
||||
"rename": "Переименовать",
|
||||
"deleteWarning": "Вы уверены, что хотите удалить этот разговор?"
|
||||
},
|
||||
"pagination": {
|
||||
"rowsPerPage": "Строк на странице",
|
||||
"pageOf": "Страница {{currentPage}} из {{totalPages}}",
|
||||
"firstPage": "Первая страница",
|
||||
"previousPage": "Предыдущая страница",
|
||||
"nextPage": "Следующая страница",
|
||||
"lastPage": "Последняя страница"
|
||||
},
|
||||
"conversation": {
|
||||
"copy": "Копировать",
|
||||
"copied": "Скопировано",
|
||||
"speak": "Озвучить",
|
||||
"answer": "Ответ",
|
||||
"edit": {
|
||||
"update": "Обновить",
|
||||
"cancel": "Отмена",
|
||||
"placeholder": "Введите обновленный запрос..."
|
||||
},
|
||||
"sources": {
|
||||
"title": "Источники",
|
||||
"text": "Текст источника",
|
||||
"link": "Ссылка на источник",
|
||||
"view_more": "Показать еще {{count}} источников"
|
||||
},
|
||||
"retry": "Повторить"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,8 +10,8 @@
|
||||
"sourceDocs": "原始文件",
|
||||
"none": "無",
|
||||
"cancel": "取消",
|
||||
"help": "聯繫支援",
|
||||
"emailUs": "寄送電子郵件給我們",
|
||||
"help": "幫助",
|
||||
"emailUs": "給我們發電郵",
|
||||
"documentation": "文件",
|
||||
"demo": [
|
||||
{
|
||||
@@ -57,45 +57,102 @@
|
||||
"name": "文件名稱",
|
||||
"date": "向量日期",
|
||||
"type": "類型",
|
||||
"tokenUsage": "Token 使用量"
|
||||
"tokenUsage": "Token 使用量",
|
||||
"noData": "沒有現有的文件",
|
||||
"searchPlaceholder": "搜尋...",
|
||||
"addNew": "新增文件",
|
||||
"preLoaded": "預載入",
|
||||
"private": "私人",
|
||||
"sync": "同步",
|
||||
"syncFrequency": {
|
||||
"never": "從不",
|
||||
"daily": "每天",
|
||||
"weekly": "每週",
|
||||
"monthly": "每月"
|
||||
},
|
||||
"actions": "操作"
|
||||
},
|
||||
"apiKeys": {
|
||||
"label": "API 金鑰",
|
||||
"label": "聊天機器人",
|
||||
"name": "名稱",
|
||||
"key": "API 金鑰",
|
||||
"sourceDoc": "來源文件",
|
||||
"createNew": "新增"
|
||||
"createNew": "新增",
|
||||
"noData": "沒有現有的聊天機器人"
|
||||
},
|
||||
"analytics": {
|
||||
"label": "分析"
|
||||
"label": "分析",
|
||||
"filterByChatbot": "按聊天機器人篩選",
|
||||
"selectChatbot": "選擇聊天機器人",
|
||||
"filterOptions": {
|
||||
"hour": "小時",
|
||||
"last24Hours": "24 小時",
|
||||
"last7Days": "7 天",
|
||||
"last15Days": "15 天",
|
||||
"last30Days": "30 天"
|
||||
},
|
||||
"messages": "訊息",
|
||||
"tokenUsage": "Token 使用量",
|
||||
"feedback": "回饋",
|
||||
"filterPlaceholder": "篩選",
|
||||
"none": "無",
|
||||
"positiveFeedback": "正向回饋",
|
||||
"negativeFeedback": "負向回饋"
|
||||
},
|
||||
"logs": {
|
||||
"label": "日誌"
|
||||
"label": "日誌",
|
||||
"filterByChatbot": "按聊天機器人篩選",
|
||||
"selectChatbot": "選擇聊天機器人",
|
||||
"none": "無",
|
||||
"tableHeader": "API 生成 / 聊天機器人會話"
|
||||
},
|
||||
"tools": {
|
||||
"label": "工具",
|
||||
"searchPlaceholder": "搜尋...",
|
||||
"addTool": "新增工具",
|
||||
"noToolsFound": "找不到工具",
|
||||
"selectToolSetup": "選擇要設定的工具",
|
||||
"settingsIconAlt": "設定圖標",
|
||||
"configureToolAria": "配置 {toolName}",
|
||||
"toggleToolAria": "切換 {toolName}"
|
||||
}
|
||||
},
|
||||
"modals": {
|
||||
"uploadDoc": {
|
||||
"label": "上傳新文件",
|
||||
"select": "選擇如何將文件上傳到 DocsGPT",
|
||||
"file": "從檔案",
|
||||
"remote": "遠端",
|
||||
"back": "返回",
|
||||
"wait": "請稍候...",
|
||||
"start": "開始對話",
|
||||
"name": "名稱",
|
||||
"choose": "選擇檔案",
|
||||
"info": "請上傳 .pdf, .txt, .rst, .docx, .md, .json, .pptx, .zip 檔案,大小限制為 25MB",
|
||||
"uploadedFiles": "已上傳的檔案",
|
||||
"info": "請上傳限制為25MB的.pdf、.txt、.rst、.csv、.xlsx、.docx、.md、.html、.epub、.json、.pptx、.zip檔案",
|
||||
"uploadedFiles": "已上傳檔案",
|
||||
"cancel": "取消",
|
||||
"train": "訓練",
|
||||
"link": "連結",
|
||||
"urlLink": "URL 連結",
|
||||
"repoUrl": "儲存庫 URL",
|
||||
"reddit": {
|
||||
"id": "用戶端 ID",
|
||||
"secret": "用戶端金鑰",
|
||||
"agent": "使用者代理(User-Agent)",
|
||||
"id": "客戶端ID",
|
||||
"secret": "客戶端密鑰",
|
||||
"agent": "使用者代理",
|
||||
"searchQueries": "搜尋查詢",
|
||||
"numberOfPosts": "貼文數量"
|
||||
"numberOfPosts": "貼文數量",
|
||||
"addQuery": "新增查詢"
|
||||
},
|
||||
"drag": {
|
||||
"title": "上傳原始檔",
|
||||
"description": "將您的文件拖放到此處以將其添加為來源"
|
||||
"title": "上傳來源檔案",
|
||||
"description": "將檔案拖放到此處以新增為來源"
|
||||
},
|
||||
"progress": {
|
||||
"upload": "正在上傳",
|
||||
"training": "正在訓練",
|
||||
"completed": "訓練完成",
|
||||
"wait": "這可能需要幾分鐘",
|
||||
"tokenLimit": "超出令牌限制,請考慮上傳較小的文檔"
|
||||
}
|
||||
},
|
||||
"createAPIKey": {
|
||||
@@ -120,7 +177,26 @@
|
||||
"shareConv": {
|
||||
"label": "建立公開頁面以分享",
|
||||
"note": "來源文件、個人資訊和後續對話將保持私密",
|
||||
"create": "建立"
|
||||
"create": "建立",
|
||||
"option": "允許使用者進行更多查詢"
|
||||
},
|
||||
"configTool": {
|
||||
"title": "工具設定",
|
||||
"type": "類型",
|
||||
"apiKeyLabel": "API 金鑰 / OAuth",
|
||||
"apiKeyPlaceholder": "輸入 API 金鑰 / OAuth",
|
||||
"addButton": "新增工具",
|
||||
"closeButton": "關閉"
|
||||
},
|
||||
"prompts": {
|
||||
"addPrompt": "新增提示",
|
||||
"addDescription": "新增自定義提示並儲存到 DocsGPT",
|
||||
"editPrompt": "編輯提示",
|
||||
"editDescription": "編輯自定義提示並儲存到 DocsGPT",
|
||||
"promptName": "提示名稱",
|
||||
"promptText": "提示文字",
|
||||
"save": "儲存",
|
||||
"nameExists": "名稱已存在"
|
||||
}
|
||||
},
|
||||
"sharedConv": {
|
||||
@@ -133,5 +209,31 @@
|
||||
"delete": "刪除",
|
||||
"rename": "重新命名",
|
||||
"deleteWarning": "您確定要刪除這個對話嗎?"
|
||||
},
|
||||
"pagination": {
|
||||
"rowsPerPage": "每頁行數",
|
||||
"pageOf": "第 {{currentPage}} 頁,共 {{totalPages}} 頁",
|
||||
"firstPage": "第一頁",
|
||||
"previousPage": "上一頁",
|
||||
"nextPage": "下一頁",
|
||||
"lastPage": "最後一頁"
|
||||
},
|
||||
"conversation": {
|
||||
"copy": "複製",
|
||||
"copied": "已複製",
|
||||
"speak": "朗讀",
|
||||
"answer": "回答",
|
||||
"edit": {
|
||||
"update": "更新",
|
||||
"cancel": "取消",
|
||||
"placeholder": "輸入更新的查詢..."
|
||||
},
|
||||
"sources": {
|
||||
"title": "來源",
|
||||
"text": "來源文字",
|
||||
"link": "來源連結",
|
||||
"view_more": "查看更多 {{count}} 個來源"
|
||||
},
|
||||
"retry": "重試"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"sourceDocs": "源",
|
||||
"none": "无",
|
||||
"cancel": "取消",
|
||||
"help": "联系支持",
|
||||
"help": "帮助",
|
||||
"emailUs": "给我们发邮件",
|
||||
"documentation": "文档",
|
||||
"demo": [
|
||||
@@ -34,7 +34,7 @@
|
||||
"settings": {
|
||||
"label": "设置",
|
||||
"general": {
|
||||
"label": "般",
|
||||
"label": "一般",
|
||||
"selectTheme": "选择主题",
|
||||
"light": "浅色",
|
||||
"dark": "暗色",
|
||||
@@ -58,7 +58,19 @@
|
||||
"date": "向量日期",
|
||||
"type": "类型",
|
||||
"tokenUsage": "令牌使用",
|
||||
"noData": "没有现有的文档"
|
||||
"noData": "没有现有的文档",
|
||||
"searchPlaceholder": "搜索...",
|
||||
"addNew": "添加新文档",
|
||||
"preLoaded": "预加载",
|
||||
"private": "私有",
|
||||
"sync": "同步",
|
||||
"syncFrequency": {
|
||||
"never": "从不",
|
||||
"daily": "每天",
|
||||
"weekly": "每周",
|
||||
"monthly": "每月"
|
||||
},
|
||||
"actions": "操作"
|
||||
},
|
||||
"apiKeys": {
|
||||
"label": "聊天机器人",
|
||||
@@ -69,10 +81,40 @@
|
||||
"noData": "没有现有的聊天机器人"
|
||||
},
|
||||
"analytics": {
|
||||
"label": "分析"
|
||||
"label": "分析",
|
||||
"filterByChatbot": "按聊天机器人筛选",
|
||||
"selectChatbot": "选择聊天机器人",
|
||||
"filterOptions": {
|
||||
"hour": "小时",
|
||||
"last24Hours": "24 小时",
|
||||
"last7Days": "7 天",
|
||||
"last15Days": "15 天",
|
||||
"last30Days": "30 天"
|
||||
},
|
||||
"messages": "消息",
|
||||
"tokenUsage": "令牌使用",
|
||||
"feedback": "反馈",
|
||||
"filterPlaceholder": "筛选",
|
||||
"none": "无",
|
||||
"positiveFeedback": "正向反馈",
|
||||
"negativeFeedback": "负向反馈"
|
||||
},
|
||||
"logs": {
|
||||
"label": "日志"
|
||||
"label": "日志",
|
||||
"filterByChatbot": "按聊天机器人筛选",
|
||||
"selectChatbot": "选择聊天机器人",
|
||||
"none": "无",
|
||||
"tableHeader": "API 生成 / 聊天机器人会话"
|
||||
},
|
||||
"tools": {
|
||||
"label": "工具",
|
||||
"searchPlaceholder": "搜索...",
|
||||
"addTool": "添加工具",
|
||||
"noToolsFound": "未找到工具",
|
||||
"selectToolSetup": "选择要设置的工具" ,
|
||||
"settingsIconAlt": "设置图标",
|
||||
"configureToolAria": "配置 {toolName}",
|
||||
"toggleToolAria": "切换 {toolName}"
|
||||
}
|
||||
},
|
||||
"modals": {
|
||||
@@ -86,7 +128,7 @@
|
||||
"start": "开始聊天",
|
||||
"name": "名称",
|
||||
"choose": "选择文件",
|
||||
"info": "请上传 .pdf, .txt, .rst, .csv, .xlsx, .docx, .md, .html, .epub, .json, .pptx, .zip 文件,限 25MB",
|
||||
"info": "请上传限制为25MB的.pdf、.txt、.rst、.csv、.xlsx、.docx、.md、.html、.epub、.json、.pptx、.zip文件",
|
||||
"uploadedFiles": "已上传文件",
|
||||
"cancel": "取消",
|
||||
"train": "训练",
|
||||
@@ -94,15 +136,23 @@
|
||||
"urlLink": "URL 链接",
|
||||
"repoUrl": "存储库 URL",
|
||||
"reddit": {
|
||||
"id": "客户端 ID",
|
||||
"id": "客户端ID",
|
||||
"secret": "客户端密钥",
|
||||
"agent": "用户代理",
|
||||
"searchQueries": "搜索查询",
|
||||
"numberOfPosts": "帖子数量"
|
||||
"numberOfPosts": "帖子数量",
|
||||
"addQuery": "添加查询"
|
||||
},
|
||||
"drag": {
|
||||
"title": "上传源文件",
|
||||
"description": "将您的文件拖放到此处以将其添加为源"
|
||||
"description": "将文件拖放到此处以添加为源"
|
||||
},
|
||||
"progress": {
|
||||
"upload": "正在上传",
|
||||
"training": "正在训练",
|
||||
"completed": "训练完成",
|
||||
"wait": "这可能需要几分钟",
|
||||
"tokenLimit": "超出令牌限制,请考虑上传较小的文档"
|
||||
}
|
||||
},
|
||||
"createAPIKey": {
|
||||
@@ -129,6 +179,24 @@
|
||||
"note": "源文档、个人信息和后续对话将保持私密",
|
||||
"create": "创建",
|
||||
"option": "允许用户进行更多查询。"
|
||||
},
|
||||
"configTool": {
|
||||
"title": "工具配置",
|
||||
"type": "类型",
|
||||
"apiKeyLabel": "API 密钥 / OAuth",
|
||||
"apiKeyPlaceholder": "输入 API 密钥 / OAuth",
|
||||
"addButton": "添加工具",
|
||||
"closeButton": "关闭"
|
||||
},
|
||||
"prompts": {
|
||||
"addPrompt": "添加提示",
|
||||
"addDescription": "添加自定义提示并保存到 DocsGPT",
|
||||
"editPrompt": "编辑提示",
|
||||
"editDescription": "编辑自定义提示并保存到 DocsGPT",
|
||||
"promptName": "提示名称",
|
||||
"promptText": "提示文本",
|
||||
"save": "保存",
|
||||
"nameExists": "名称已存在"
|
||||
}
|
||||
},
|
||||
"sharedConv": {
|
||||
@@ -141,5 +209,31 @@
|
||||
"delete": "删除",
|
||||
"rename": "重命名",
|
||||
"deleteWarning": "您确定要删除此对话吗?"
|
||||
},
|
||||
"pagination": {
|
||||
"rowsPerPage": "每页行数",
|
||||
"pageOf": "第 {{currentPage}} 页,共 {{totalPages}} 页",
|
||||
"firstPage": "第一页",
|
||||
"previousPage": "上一页",
|
||||
"nextPage": "下一页",
|
||||
"lastPage": "最后一页"
|
||||
},
|
||||
"conversation": {
|
||||
"copy": "复制",
|
||||
"copied": "已复制",
|
||||
"speak": "朗读",
|
||||
"answer": "回答",
|
||||
"edit": {
|
||||
"update": "更新",
|
||||
"cancel": "取消",
|
||||
"placeholder": "输入更新的查询..."
|
||||
},
|
||||
"sources": {
|
||||
"title": "来源",
|
||||
"text": "来源文本",
|
||||
"link": "来源链接",
|
||||
"view_more": "更多{{count}}个来源"
|
||||
},
|
||||
"retry": "重试"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
import userService from '../api/services/userService';
|
||||
import Exit from '../assets/exit.svg';
|
||||
import { ActiveState } from '../models/misc';
|
||||
import { AvailableTool } from './types';
|
||||
import ConfigToolModal from './ConfigToolModal';
|
||||
import { useOutsideAlerter } from '../hooks';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function AddToolModal({
|
||||
message,
|
||||
@@ -25,6 +26,14 @@ export default function AddToolModal({
|
||||
);
|
||||
const [configModalState, setConfigModalState] =
|
||||
React.useState<ActiveState>('INACTIVE');
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useOutsideAlerter(modalRef, () => {
|
||||
if (modalState === 'ACTIVE') {
|
||||
setModalState('INACTIVE');
|
||||
}
|
||||
}, [modalState]);
|
||||
|
||||
const getAvailableTools = () => {
|
||||
userService
|
||||
@@ -63,14 +72,18 @@ export default function AddToolModal({
|
||||
React.useEffect(() => {
|
||||
if (modalState === 'ACTIVE') getAvailableTools();
|
||||
}, [modalState]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`${
|
||||
modalState === 'ACTIVE' ? 'visible' : 'hidden'
|
||||
} fixed top-0 left-0 z-30 h-screen w-screen bg-gray-alpha flex items-center justify-center`}
|
||||
} fixed top-0 left-0 z-30 h-screen w-screen bg-gray-alpha flex items-center justify-center`}
|
||||
>
|
||||
<article className="flex h-[85vh] w-[90vw] md:w-[75vw] flex-col gap-4 rounded-2xl bg-[#FBFBFB] shadow-lg dark:bg-[#26272E]">
|
||||
<article
|
||||
ref={modalRef}
|
||||
className="flex h-[85vh] w-[90vw] md:w-[75vw] flex-col gap-4 rounded-2xl bg-[#FBFBFB] shadow-lg dark:bg-[#26272E]"
|
||||
>
|
||||
<div className="relative">
|
||||
<button
|
||||
className="absolute top-3 right-4 m-2 w-3"
|
||||
@@ -78,13 +91,17 @@ export default function AddToolModal({
|
||||
setModalState('INACTIVE');
|
||||
}}
|
||||
>
|
||||
<img className="filter dark:invert" src={Exit} />
|
||||
<img
|
||||
className="filter dark:invert"
|
||||
src={Exit}
|
||||
alt={t('cancel')}
|
||||
/>
|
||||
</button>
|
||||
<div className="p-6">
|
||||
<h2 className="font-semibold text-xl text-jet dark:text-bright-gray px-3">
|
||||
Select a tool to set up
|
||||
{t('settings.tools.selectToolSetup')}
|
||||
</h2>
|
||||
<div className="mt-5 grid grid-cols-3 gap-4 h-[73vh] overflow-auto px-3 py-px">
|
||||
<div className="mt-5 flex flex-col sm:grid sm:grid-cols-3 gap-4 h-[73vh] overflow-auto px-3 py-px">
|
||||
{availableTools.map((tool, index) => (
|
||||
<div
|
||||
role="button"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Exit from '../assets/exit.svg';
|
||||
import Input from '../components/Input';
|
||||
@@ -17,6 +18,7 @@ export default function ConfigToolModal({
|
||||
tool: AvailableTool | null;
|
||||
getUserTools: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [authKey, setAuthKey] = React.useState<string>('');
|
||||
|
||||
const handleAddTool = (tool: AvailableTool) => {
|
||||
@@ -52,21 +54,22 @@ export default function ConfigToolModal({
|
||||
</button>
|
||||
<div className="p-6">
|
||||
<h2 className="font-semibold text-xl text-jet dark:text-bright-gray px-3">
|
||||
Tool Config
|
||||
{t('modals.configTool.title')}
|
||||
</h2>
|
||||
<p className="mt-5 text-sm text-gray-600 dark:text-gray-400 px-3">
|
||||
Type: <span className="font-semibold">{tool?.name} </span>
|
||||
{t('modals.configTool.type')}:{' '}
|
||||
<span className="font-semibold">{tool?.name} </span>
|
||||
</p>
|
||||
<div className="mt-6 relative px-3">
|
||||
<span className="absolute left-5 -top-2 bg-white px-2 text-xs text-gray-4000 dark:bg-[#26272E] dark:text-silver">
|
||||
API Key / Oauth
|
||||
{t('modals.configTool.apiKeyLabel')}
|
||||
</span>
|
||||
<Input
|
||||
type="text"
|
||||
value={authKey}
|
||||
onChange={(e) => setAuthKey(e.target.value)}
|
||||
borderVariant="thin"
|
||||
placeholder="Enter API Key / Oauth"
|
||||
placeholder={t('modals.configTool.apiKeyPlaceholder')}
|
||||
></Input>
|
||||
</div>
|
||||
<div className="mt-8 flex flex-row-reverse gap-1 px-3">
|
||||
@@ -76,7 +79,7 @@ export default function ConfigToolModal({
|
||||
}}
|
||||
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]"
|
||||
>
|
||||
Add Tool
|
||||
{t('modals.configTool.addButton')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
@@ -84,7 +87,7 @@ export default function ConfigToolModal({
|
||||
}}
|
||||
className="cursor-pointer rounded-3xl px-5 py-2 text-sm font-medium hover:bg-gray-100 dark:bg-transparent dark:text-light-gray dark:hover:bg-[#767183]/50"
|
||||
>
|
||||
Close
|
||||
{t('modals.configTool.closeButton')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { ActiveState } from '../models/misc';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { ActiveState } from '../models/misc';
|
||||
import WrapperModal from './WrapperModal';
|
||||
function ConfirmationModal({
|
||||
|
||||
export default function ConfirmationModal({
|
||||
message,
|
||||
modalState,
|
||||
setModalState,
|
||||
@@ -29,7 +31,7 @@ function ConfirmationModal({
|
||||
}}
|
||||
>
|
||||
<div className="relative">
|
||||
<div className="p-8">
|
||||
<div>
|
||||
<p className="font-base mb-1 w-[90%] text-lg text-jet dark:text-bright-gray">
|
||||
{message}
|
||||
</p>
|
||||
@@ -59,5 +61,3 @@ function ConfirmationModal({
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default ConfirmationModal;
|
||||
|
||||
@@ -101,11 +101,17 @@ export const ShareConversationModal = ({
|
||||
return (
|
||||
<WrapperModal close={close}>
|
||||
<div className="flex flex-col gap-2">
|
||||
<h2 className="text-xl font-medium">{t('modals.shareConv.label')}</h2>
|
||||
<p className="text-sm">{t('modals.shareConv.note')}</p>
|
||||
<h2 className="text-xl font-medium text-eerie-black dark:text-white">
|
||||
{t('modals.shareConv.label')}
|
||||
</h2>
|
||||
<p className="text-sm text-eerie-black dark:text-white">
|
||||
{t('modals.shareConv.note')}
|
||||
</p>
|
||||
<div className="flex items-center justify-between">
|
||||
<span className="text-lg">{t('modals.shareConv.option')}</span>
|
||||
<label className=" cursor-pointer select-none items-center">
|
||||
<span className="text-lg text-eerie-black dark:text-white">
|
||||
{t('modals.shareConv.option')}
|
||||
</span>
|
||||
<label className="cursor-pointer select-none items-center">
|
||||
<div className="relative">
|
||||
<input
|
||||
type="checkbox"
|
||||
@@ -143,7 +149,7 @@ export const ShareConversationModal = ({
|
||||
</div>
|
||||
)}
|
||||
<div className="flex items-baseline justify-between gap-2">
|
||||
<span className="no-scrollbar w-full overflow-x-auto whitespace-nowrap rounded-full border-2 py-3 px-4">
|
||||
<span className="no-scrollbar w-full overflow-x-auto whitespace-nowrap rounded-full border-2 py-3 px-4 text-eerie-black dark:text-white">
|
||||
{`${domain}/share/${identifier ?? '....'}`}
|
||||
</span>
|
||||
{status === 'fetched' ? (
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
import { WrapperModalProps } from './types';
|
||||
import Exit from '../assets/exit.svg';
|
||||
|
||||
const WrapperModal: React.FC<WrapperModalProps> = ({
|
||||
import Exit from '../assets/exit.svg';
|
||||
import { WrapperModalProps } from './types';
|
||||
|
||||
export default function WrapperModal({
|
||||
children,
|
||||
close,
|
||||
isPerformingTask,
|
||||
}) => {
|
||||
}: WrapperModalProps) {
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -39,10 +40,13 @@ const WrapperModal: React.FC<WrapperModalProps> = ({
|
||||
<div className="fixed top-0 left-0 z-30 flex h-screen w-screen items-center justify-center bg-gray-alpha bg-opacity-50">
|
||||
<div
|
||||
ref={modalRef}
|
||||
className="relative w-11/12 rounded-2xl bg-white p-10 dark:bg-outer-space sm:w-[512px]"
|
||||
className="relative w-11/12 rounded-2xl bg-white dark:bg-outer-space sm:w-[512px] p-8"
|
||||
>
|
||||
{!isPerformingTask && (
|
||||
<button className="absolute top-3 right-4 m-2 w-3" onClick={close}>
|
||||
<button
|
||||
className="absolute top-3 right-4 m-2 w-3 z-50"
|
||||
onClick={close}
|
||||
>
|
||||
<img className="filter dark:invert" src={Exit} />
|
||||
</button>
|
||||
)}
|
||||
@@ -50,6 +54,4 @@ const WrapperModal: React.FC<WrapperModalProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default WrapperModal;
|
||||
}
|
||||
|
||||
@@ -14,4 +14,5 @@ export type WrapperModalProps = {
|
||||
children?: React.ReactNode;
|
||||
isPerformingTask?: boolean;
|
||||
close: () => void;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
@@ -2,6 +2,7 @@ import { ActiveState } from '../models/misc';
|
||||
import Exit from '../assets/exit.svg';
|
||||
import Input from '../components/Input';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
function AddPrompt({
|
||||
setModalState,
|
||||
@@ -20,6 +21,8 @@ function AddPrompt({
|
||||
setNewPromptContent: (content: string) => void;
|
||||
disableSave: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<button
|
||||
@@ -29,19 +32,23 @@ function AddPrompt({
|
||||
setNewPromptName('');
|
||||
setNewPromptContent('');
|
||||
}}
|
||||
aria-label="Close add prompt modal"
|
||||
>
|
||||
<img className="filter dark:invert" src={Exit} />
|
||||
<img className="filter dark:invert" src={Exit} alt="Close modal" />
|
||||
</button>
|
||||
<div className="p-8">
|
||||
<p className="mb-1 text-xl text-jet dark:text-bright-gray">
|
||||
Add Prompt
|
||||
{t('modals.prompts.addPrompt')}
|
||||
</p>
|
||||
<p className="mb-7 text-xs text-[#747474] dark:text-[#7F7F82]">
|
||||
Add your custom prompt and save it to DocsGPT
|
||||
{t('modals.prompts.addDescription')}
|
||||
</p>
|
||||
<div>
|
||||
<label htmlFor="new-prompt-name" className="sr-only">
|
||||
Prompt Name
|
||||
</label>
|
||||
<Input
|
||||
placeholder="Prompt Name"
|
||||
placeholder={t('modals.prompts.promptName')}
|
||||
type="text"
|
||||
className="h-10 rounded-lg"
|
||||
value={newPromptName}
|
||||
@@ -49,18 +56,23 @@ function AddPrompt({
|
||||
/>
|
||||
<div className="relative bottom-12 left-3 mt-[-3.00px]">
|
||||
<span className="bg-white px-1 text-xs text-silver dark:bg-outer-space dark:text-silver">
|
||||
Prompt Name
|
||||
{t('modals.prompts.promptName')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative top-[7px] left-3">
|
||||
<span className="bg-white px-1 text-xs text-silver dark:bg-outer-space dark:text-silver">
|
||||
Prompt Text
|
||||
{t('modals.prompts.promptText')}
|
||||
</span>
|
||||
</div>
|
||||
<label htmlFor="new-prompt-content" className="sr-only">
|
||||
Prompt Text
|
||||
</label>
|
||||
<textarea
|
||||
id="new-prompt-content"
|
||||
className="h-56 w-full rounded-lg border-2 border-silver px-3 py-2 outline-none dark:border-silver/40 dark:bg-transparent dark:text-white"
|
||||
value={newPromptContent}
|
||||
onChange={(e) => setNewPromptContent(e.target.value)}
|
||||
aria-label="Prompt Text"
|
||||
></textarea>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-row-reverse">
|
||||
@@ -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')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -97,6 +111,8 @@ function EditPrompt({
|
||||
currentPromptEdit: { name: string; id: string; type: string };
|
||||
disableSave: boolean;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<button
|
||||
@@ -104,38 +120,47 @@ function EditPrompt({
|
||||
onClick={() => {
|
||||
setModalState('INACTIVE');
|
||||
}}
|
||||
aria-label="Close edit prompt modal"
|
||||
>
|
||||
<img className="filter dark:invert" src={Exit} />
|
||||
<img className="filter dark:invert" src={Exit} alt="Close modal" />
|
||||
</button>
|
||||
<div className="p-8">
|
||||
<p className="mb-1 text-xl text-jet dark:text-bright-gray">
|
||||
Edit Prompt
|
||||
{t('modals.prompts.editPrompt')}
|
||||
</p>
|
||||
<p className="mb-7 text-xs text-[#747474] dark:text-[#7F7F82]">
|
||||
Edit your custom prompt and save it to DocsGPT
|
||||
{t('modals.prompts.editDescription')}
|
||||
</p>
|
||||
<div>
|
||||
<label htmlFor="edit-prompt-name" className="sr-only">
|
||||
Prompt Name
|
||||
</label>
|
||||
<Input
|
||||
placeholder="Prompt Name"
|
||||
placeholder={t('modals.prompts.promptName')}
|
||||
type="text"
|
||||
className="h-10 rounded-lg"
|
||||
value={editPromptName}
|
||||
onChange={(e) => setEditPromptName(e.target.value)}
|
||||
></Input>
|
||||
/>
|
||||
<div className="relative bottom-12 left-3 mt-[-3.00px]">
|
||||
<span className="bg-white px-1 text-xs text-silver dark:bg-outer-space dark:text-silver">
|
||||
Prompt Name
|
||||
{t('modals.prompts.promptName')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative top-[7px] left-3">
|
||||
<span className="bg-white px-1 text-xs text-silver dark:bg-outer-space dark:text-silver">
|
||||
Prompt Text
|
||||
{t('modals.prompts.promptText')}
|
||||
</span>
|
||||
</div>
|
||||
<label htmlFor="edit-prompt-content" className="sr-only">
|
||||
Prompt Text
|
||||
</label>
|
||||
<textarea
|
||||
id="edit-prompt-content"
|
||||
className="h-56 w-full rounded-lg border-2 border-silver px-3 py-2 outline-none dark:border-silver/40 dark:bg-transparent dark:text-white"
|
||||
value={editPromptContent}
|
||||
onChange={(e) => setEditPromptContent(e.target.value)}
|
||||
aria-label="Prompt Text"
|
||||
></textarea>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-row-reverse gap-4">
|
||||
@@ -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')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -115,12 +115,20 @@ export default function APIKeys() {
|
||||
<table className="min-w-full divide-y divide-silver dark:divide-silver/40 ">
|
||||
<thead>
|
||||
<tr className="text-start text-sm font-medium text-gray-700 dark:text-gray-50 uppercase">
|
||||
<th className="p-2">{t('settings.apiKeys.name')}</th>
|
||||
<th className="p-2">
|
||||
<th scope="col" className="p-2">
|
||||
{t('settings.apiKeys.name')}
|
||||
</th>
|
||||
<th scope="col" className="p-2">
|
||||
{t('settings.apiKeys.sourceDoc')}
|
||||
</th>
|
||||
<th className="p-2">{t('settings.apiKeys.key')}</th>
|
||||
<th></th>
|
||||
<th scope="col" className="p-2">
|
||||
{t('settings.apiKeys.key')}
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
className="p-2"
|
||||
aria-label="Actions"
|
||||
></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-200 dark:divide-neutral-700">
|
||||
@@ -146,7 +154,7 @@ export default function APIKeys() {
|
||||
<td>
|
||||
<img
|
||||
src={Trash}
|
||||
alt="Delete"
|
||||
alt={`Delete ${element.name}`}
|
||||
className="h-4 w-4 cursor-pointer hover:opacity-50"
|
||||
id={`img-${index}`}
|
||||
onClick={() => handleDeleteKey(element.id)}
|
||||
|
||||
@@ -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<Record<
|
||||
string,
|
||||
number
|
||||
@@ -54,15 +69,24 @@ export default function Analytics() {
|
||||
const [messagesFilter, setMessagesFilter] = useState<{
|
||||
label: string;
|
||||
value: string;
|
||||
}>({ 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() {
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="flex flex-col gap-3">
|
||||
<p className="font-bold text-jet dark:text-bright-gray">
|
||||
Filter by chatbot
|
||||
{t('settings.analytics.filterByChatbot')}
|
||||
</p>
|
||||
<Dropdown
|
||||
size="w-[55vw] sm:w-[360px]"
|
||||
@@ -174,9 +198,9 @@ export default function Analytics() {
|
||||
label: chatbot.name,
|
||||
value: chatbot.id,
|
||||
})),
|
||||
{ label: 'None', value: '' },
|
||||
{ label: t('settings.analytics.none'), value: '' },
|
||||
]}
|
||||
placeholder="Select chatbot"
|
||||
placeholder={t('settings.analytics.selectChatbot')}
|
||||
onSelect={(chatbot: { label: string; value: string }) => {
|
||||
setSelectedChatbot(
|
||||
chatbots.find((item) => item.id === chatbot.value),
|
||||
@@ -191,6 +215,7 @@ export default function Analytics() {
|
||||
}
|
||||
rounded="3xl"
|
||||
border="border"
|
||||
borderColor="gray-700"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -199,12 +224,12 @@ export default function Analytics() {
|
||||
<div className="h-[345px] [@media(min-width:1080px)]:w-1/2 w-full px-6 py-5 border rounded-2xl border-silver dark:border-silver/40 overflow-hidden">
|
||||
<div className="flex flex-row items-center justify-start gap-3">
|
||||
<p className="font-bold text-jet dark:text-bright-gray">
|
||||
Messages
|
||||
{t('settings.analytics.messages')}
|
||||
</p>
|
||||
<Dropdown
|
||||
size="w-[125px]"
|
||||
options={filterOptions}
|
||||
placeholder="Filter"
|
||||
placeholder={t('settings.analytics.filterPlaceholder')}
|
||||
onSelect={(selectedOption: {
|
||||
label: string;
|
||||
value: string;
|
||||
@@ -232,7 +257,7 @@ export default function Analytics() {
|
||||
),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Messages',
|
||||
label: t('settings.analytics.messages'),
|
||||
data: Object.values(messagesData || {}),
|
||||
backgroundColor: '#7D54D1',
|
||||
},
|
||||
@@ -250,12 +275,12 @@ export default function Analytics() {
|
||||
<div className="h-[345px] [@media(min-width:1080px)]:w-1/2 w-full px-6 py-5 border rounded-2xl border-silver dark:border-silver/40 overflow-hidden">
|
||||
<div className="flex flex-row items-center justify-start gap-3">
|
||||
<p className="font-bold text-jet dark:text-bright-gray">
|
||||
Token Usage
|
||||
{t('settings.analytics.tokenUsage')}
|
||||
</p>
|
||||
<Dropdown
|
||||
size="w-[125px]"
|
||||
options={filterOptions}
|
||||
placeholder="Filter"
|
||||
placeholder={t('settings.analytics.filterPlaceholder')}
|
||||
onSelect={(selectedOption: {
|
||||
label: string;
|
||||
value: string;
|
||||
@@ -283,7 +308,7 @@ export default function Analytics() {
|
||||
),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Tokens',
|
||||
label: t('settings.analytics.tokenUsage'),
|
||||
data: Object.values(tokenUsageData || {}),
|
||||
backgroundColor: '#7D54D1',
|
||||
},
|
||||
@@ -303,12 +328,12 @@ export default function Analytics() {
|
||||
<div className="h-[345px] w-full px-6 py-5 border rounded-2xl border-silver dark:border-silver/40 overflow-hidden">
|
||||
<div className="flex flex-row items-center justify-start gap-3">
|
||||
<p className="font-bold text-jet dark:text-bright-gray">
|
||||
Feedback
|
||||
{t('settings.analytics.feedback')}
|
||||
</p>
|
||||
<Dropdown
|
||||
size="w-[125px]"
|
||||
options={filterOptions}
|
||||
placeholder="Filter"
|
||||
placeholder={t('settings.analytics.filterPlaceholder')}
|
||||
onSelect={(selectedOption: {
|
||||
label: string;
|
||||
value: string;
|
||||
@@ -336,14 +361,14 @@ export default function Analytics() {
|
||||
),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Positive Feedback',
|
||||
label: t('settings.analytics.positiveFeedback'),
|
||||
data: Object.values(feedbackData || {}).map(
|
||||
(item) => item.positive,
|
||||
),
|
||||
backgroundColor: '#7D54D1',
|
||||
},
|
||||
{
|
||||
label: 'Negative Feedback',
|
||||
label: t('settings.analytics.negativeFeedback'),
|
||||
data: Object.values(feedbackData || {}).map(
|
||||
(item) => item.negative,
|
||||
),
|
||||
|
||||
@@ -54,10 +54,10 @@ const Documents: React.FC<DocumentsProps> = ({
|
||||
const [totalPages, setTotalPages] = useState<number>(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<DocumentsProps> = ({
|
||||
<div className="z-10 w-full overflow-x-auto">
|
||||
<div className="my-3 flex justify-between items-center">
|
||||
<div className="p-1">
|
||||
<label htmlFor="document-search-input" className="sr-only">
|
||||
{t('settings.documents.searchPlaceholder')}
|
||||
</label>
|
||||
<Input
|
||||
maxLength={256}
|
||||
placeholder="Search..."
|
||||
placeholder={t('settings.documents.searchPlaceholder')}
|
||||
name="Document-search-input"
|
||||
type="text"
|
||||
id="document-search-input"
|
||||
@@ -161,27 +164,24 @@ const Documents: React.FC<DocumentsProps> = ({
|
||||
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
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="rounded-full w-40 bg-purple-30 px-4 py-3 text-white hover:bg-[#6F3FD1]"
|
||||
title="Add New Document"
|
||||
title={t('settings.documents.addNew')}
|
||||
onClick={() => {
|
||||
setIsOnboarding(false); // Set onboarding flag if needed
|
||||
setModalState('ACTIVE'); // Open the upload modal
|
||||
setIsOnboarding(false);
|
||||
setModalState('ACTIVE');
|
||||
}}
|
||||
>
|
||||
Add New
|
||||
{t('settings.documents.addNew')}
|
||||
</button>
|
||||
</div>
|
||||
{loading ? (
|
||||
<SkeletonLoader count={1} />
|
||||
) : (
|
||||
<div className="flex flex-col">
|
||||
<div className="flex flex-col">
|
||||
<div className="flex-grow">
|
||||
<div className="dark:border-silver/40 border-silver rounded-md border overflow-auto">
|
||||
<table className="min-w-full divide-y divide-silver dark:divide-silver/40 text-xs sm:text-sm ">
|
||||
@@ -224,9 +224,10 @@ const Documents: React.FC<DocumentsProps> = ({
|
||||
*/}
|
||||
<th
|
||||
scope="col"
|
||||
className="px-6 py-2 text-start font-medium text-gray-700 dark:text-gray-50 uppercase"
|
||||
className="px-6 py-2 text-start font-medium text-gray-700 dark:text-gray-50 uppercase sr-only"
|
||||
aria-label={t('settings.documents.actions')}
|
||||
>
|
||||
{' '}
|
||||
{t('settings.documents.actions')}
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -270,7 +271,7 @@ const Documents: React.FC<DocumentsProps> = ({
|
||||
{document.type !== 'remote' && (
|
||||
<img
|
||||
src={Trash}
|
||||
alt="Delete"
|
||||
alt={t('convTile.delete')}
|
||||
className="h-4 w-4 cursor-pointer opacity-60 hover:opacity-100"
|
||||
id={`img-${index}`}
|
||||
onClick={(event) => {
|
||||
@@ -282,7 +283,7 @@ const Documents: React.FC<DocumentsProps> = ({
|
||||
{document.syncFrequency && (
|
||||
<div className="ml-2">
|
||||
<DropdownMenu
|
||||
name="Sync"
|
||||
name={t('settings.documents.sync')}
|
||||
options={syncOptions}
|
||||
onSelect={(value: string) => {
|
||||
handleManageSync(document, value);
|
||||
|
||||
@@ -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 (
|
||||
<div className="mt-12">
|
||||
<div className="mb-5">
|
||||
<p className="font-bold text-jet dark:text-bright-gray">
|
||||
<label className="block font-bold text-jet dark:text-bright-gray">
|
||||
{t('settings.general.selectTheme')}
|
||||
</p>
|
||||
</label>
|
||||
<Dropdown
|
||||
options={themes}
|
||||
selectedValue={selectedTheme}
|
||||
onSelect={(option: string) => {
|
||||
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() {
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-5">
|
||||
<p className="mb-2 font-bold text-jet dark:text-bright-gray">
|
||||
<label className="block mb-2 font-bold text-jet dark:text-bright-gray">
|
||||
{t('settings.general.selectLanguage')}
|
||||
</p>
|
||||
</label>
|
||||
<Dropdown
|
||||
options={languageOptions.filter(
|
||||
(languageOption) =>
|
||||
@@ -133,9 +120,9 @@ export default function General() {
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-5">
|
||||
<p className="font-bold text-jet dark:text-bright-gray">
|
||||
<label className="block font-bold text-jet dark:text-bright-gray">
|
||||
{t('settings.general.chunks')}
|
||||
</p>
|
||||
</label>
|
||||
<Dropdown
|
||||
options={chunks}
|
||||
selectedValue={selectedChunks}
|
||||
@@ -146,9 +133,9 @@ export default function General() {
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-5">
|
||||
<p className="mb-2 font-bold text-jet dark:text-bright-gray">
|
||||
<label className="mb-2 block font-bold text-jet dark:text-bright-gray">
|
||||
{t('settings.general.convHistory')}
|
||||
</p>
|
||||
</label>
|
||||
<Dropdown
|
||||
options={Array.from(token_limits, ([value, desc]) => ({
|
||||
value: value,
|
||||
@@ -181,16 +168,14 @@ export default function General() {
|
||||
/>
|
||||
</div>
|
||||
<div className="w-56">
|
||||
<p className="font-bold text-jet dark:text-bright-gray">
|
||||
<label className="block font-bold text-jet dark:text-bright-gray">
|
||||
{t('settings.general.deleteAllLabel')}
|
||||
</p>
|
||||
</label>
|
||||
<button
|
||||
className="mt-2 flex w-full cursor-pointer items-center justify-between rounded-3xl border border-solid border-red-500 px-5 py-3 text-red-500 hover:bg-red-500 hover:text-white"
|
||||
className="mt-2 flex w-full cursor-pointer items-center justify-between rounded-3xl border border-solid border-red-700 px-5 py-3 text-red-700 transition-colors hover:bg-red-700 hover:text-white dark:border-red-600 dark:text-red-600 dark:hover:bg-red-600 dark:hover:text-white"
|
||||
onClick={() => dispatch(setModalStateDeleteConv('ACTIVE'))}
|
||||
>
|
||||
<span className="overflow-hidden text-ellipsis ">
|
||||
{t('settings.general.deleteAllBtn')}
|
||||
</span>
|
||||
{t('settings.general.deleteAllBtn')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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<APIKeyData[]>([]);
|
||||
const [selectedChatbot, setSelectedChatbot] = useState<APIKeyData | null>();
|
||||
const [logs, setLogs] = useState<LogData[]>([]);
|
||||
@@ -65,9 +67,12 @@ export default function Logs() {
|
||||
<div className="mt-12">
|
||||
<div className="flex flex-col items-start">
|
||||
<div className="flex flex-col gap-3">
|
||||
<p className="font-bold text-jet dark:text-bright-gray">
|
||||
Filter by chatbot
|
||||
</p>
|
||||
<label
|
||||
id="chatbot-filter-label"
|
||||
className="font-bold text-jet dark:text-bright-gray"
|
||||
>
|
||||
{t('settings.logs.filterByChatbot')}
|
||||
</label>
|
||||
{loadingChatbots ? (
|
||||
<SkeletonLoader />
|
||||
) : (
|
||||
@@ -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<any>();
|
||||
const firstObserver = useCallback((node: HTMLDivElement) => {
|
||||
if (observerRef.current) {
|
||||
@@ -134,7 +140,7 @@ function LogsTable({ logs, setPage }: LogsTableProps) {
|
||||
<div className="logs-table border rounded-2xl h-[55vh] w-full overflow-hidden border-silver dark:border-silver/40">
|
||||
<div className="h-8 bg-black/10 dark:bg-chinese-black flex flex-col items-start justify-center">
|
||||
<p className="px-3 text-xs dark:text-gray-6000">
|
||||
API generated / chatbot conversations
|
||||
{t('settings.logs.tableHeader')}
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
@@ -156,6 +162,7 @@ function LogsTable({ logs, setPage }: LogsTableProps) {
|
||||
}
|
||||
|
||||
function Log({ log }: { log: LogData }) {
|
||||
const { t } = useTranslation();
|
||||
const logLevelColor = {
|
||||
info: 'text-green-500',
|
||||
error: 'text-red-500',
|
||||
@@ -167,7 +174,7 @@ function Log({ log }: { log: LogData }) {
|
||||
<summary className="flex flex-row items-center gap-2 text-gray-900 cursor-pointer p-2 group-open:bg-[#F9F9F9] dark:group-open:bg-dark-charcoal">
|
||||
<img
|
||||
src={ChevronRight}
|
||||
alt="chevron-right"
|
||||
alt="Expand log entry"
|
||||
className="w-3 h-3 transition duration-300 group-open:rotate-90"
|
||||
/>
|
||||
<span className="flex flex-row gap-2">
|
||||
|
||||
@@ -168,7 +168,7 @@ export default function Prompts({
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="mt-[24px] rounded-3xl border border-solid border-purple-30 px-5 py-3 text-purple-30 hover:bg-purple-30 hover:text-white"
|
||||
className="mt-[24px] rounded-3xl border border-solid border-purple-700 px-5 py-3 text-purple-700 transition-colors hover:bg-purple-700 hover:text-white dark:border-purple-400 dark:text-purple-400 dark:hover:bg-purple-400 dark:hover:text-white"
|
||||
onClick={() => {
|
||||
setModalType('ADD');
|
||||
setModalState('ACTIVE');
|
||||
|
||||
@@ -58,6 +58,12 @@ export default function ToolConfig({
|
||||
handleGoBack();
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
userService.deleteTool({ id: tool.id }).then(() => {
|
||||
handleGoBack();
|
||||
});
|
||||
};
|
||||
return (
|
||||
<div className="mt-8 flex flex-col gap-4">
|
||||
<div className="mb-4 flex items-center gap-3 text-eerie-black dark:text-bright-gray text-sm">
|
||||
@@ -83,7 +89,7 @@ export default function ToolConfig({
|
||||
Authentication
|
||||
</p>
|
||||
)}
|
||||
<div className="mt-4 flex items-center gap-2">
|
||||
<div className="flex mt-4 flex-col sm:flex-row items-start sm:items-center gap-2">
|
||||
{Object.keys(tool?.config).length !== 0 && (
|
||||
<div className="relative w-96">
|
||||
<span className="absolute left-5 -top-2 bg-white px-2 text-xs text-gray-4000 dark:bg-[#26272E] dark:text-silver">
|
||||
@@ -98,12 +104,20 @@ export default function ToolConfig({
|
||||
></Input>
|
||||
</div>
|
||||
)}
|
||||
<button
|
||||
className="rounded-full h-10 w-36 bg-purple-30 text-white hover:bg-[#6F3FD1] text-nowrap text-sm"
|
||||
onClick={handleSaveChanges}
|
||||
>
|
||||
Save changes
|
||||
</button>
|
||||
<div className="flex items-center gap-2">
|
||||
<button
|
||||
className="rounded-full px-5 py-[10px] bg-purple-30 text-white hover:bg-[#6F3FD1] text-nowrap text-sm"
|
||||
onClick={handleSaveChanges}
|
||||
>
|
||||
Save changes
|
||||
</button>
|
||||
<button
|
||||
className="rounded-full px-5 py-[10px] border border-solid border-red-500 text-red-500 hover:bg-red-500 hover:text-white text-nowrap text-sm"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col gap-4">
|
||||
@@ -118,7 +132,7 @@ export default function ToolConfig({
|
||||
key={actionIndex}
|
||||
className="w-full border border-silver dark:border-silver/40 rounded-xl"
|
||||
>
|
||||
<div className="h-10 bg-[#F9F9F9] dark:bg-[#28292D] rounded-t-xl border-b border-silver dark:border-silver/40 flex items-center justify-between px-5">
|
||||
<div className="h-10 bg-[#F9F9F9] dark:bg-[#28292D] rounded-t-xl border-b border-silver dark:border-silver/40 flex items-center justify-between px-5 flex-wrap">
|
||||
<p className="font-semibold text-eerie-black dark:text-bright-gray">
|
||||
{action.name}
|
||||
</p>
|
||||
@@ -146,7 +160,7 @@ export default function ToolConfig({
|
||||
<span className="absolute inset-y-0 start-0 m-[3px] size-[18px] rounded-full bg-white transition-all peer-checked:start-4"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="mt-5 relative px-5 w-96">
|
||||
<div className="mt-5 relative px-5 w-full sm:w-96">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Enter description"
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import userService from '../api/services/userService';
|
||||
import CogwheelIcon from '../assets/cogwheel.svg';
|
||||
import NoFilesDarkIcon from '../assets/no-files-dark.svg';
|
||||
import NoFilesIcon from '../assets/no-files.svg';
|
||||
import Input from '../components/Input';
|
||||
import { useDarkTheme } from '../hooks';
|
||||
import AddToolModal from '../modals/AddToolModal';
|
||||
import { ActiveState } from '../models/misc';
|
||||
import { UserTool } from './types';
|
||||
import ToolConfig from './ToolConfig';
|
||||
import { UserTool } from './types';
|
||||
|
||||
export default function Tools() {
|
||||
const { t } = useTranslation();
|
||||
const [isDarkTheme] = useDarkTheme();
|
||||
const [searchTerm, setSearchTerm] = React.useState('');
|
||||
const [addToolModalState, setAddToolModalState] =
|
||||
React.useState<ActiveState>('INACTIVE');
|
||||
@@ -66,86 +72,113 @@ export default function Tools() {
|
||||
<div className="flex flex-col relative">
|
||||
<div className="my-3 flex justify-between items-center gap-1">
|
||||
<div className="p-1">
|
||||
<label htmlFor="tool-search-input" className="sr-only">
|
||||
{t('settings.tools.searchPlaceholder')}
|
||||
</label>
|
||||
<Input
|
||||
maxLength={256}
|
||||
placeholder="Search..."
|
||||
placeholder={t('settings.tools.searchPlaceholder')}
|
||||
name="Document-search-input"
|
||||
type="text"
|
||||
id="document-search-input"
|
||||
id="tool-search-input"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="rounded-full w-40 bg-purple-30 px-4 py-3 text-white hover:bg-[#6F3FD1] text-nowrap"
|
||||
className="rounded-full min-w-[160px] bg-purple-30 px-6 py-3 text-white hover:bg-[#6F3FD1] text-nowrap"
|
||||
onClick={() => {
|
||||
setAddToolModalState('ACTIVE');
|
||||
}}
|
||||
>
|
||||
Add Tool
|
||||
{t('settings.tools.addTool')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
{userTools
|
||||
.filter((tool) =>
|
||||
tool.displayName
|
||||
.toLowerCase()
|
||||
.includes(searchTerm.toLowerCase()),
|
||||
)
|
||||
.map((tool, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="relative h-56 w-full p-6 border rounded-2xl border-silver dark:border-silver/40 flex flex-col justify-between"
|
||||
>
|
||||
<div className="w-full">
|
||||
<div className="w-full flex items-center justify-between">
|
||||
<img
|
||||
src={`/toolIcons/tool_${tool.name}.svg`}
|
||||
className="h-8 w-8"
|
||||
/>
|
||||
<button
|
||||
className="absolute top-3 right-3 cursor-pointer"
|
||||
onClick={() => handleSettingsClick(tool)}
|
||||
>
|
||||
{userTools.filter((tool) =>
|
||||
tool.displayName
|
||||
.toLowerCase()
|
||||
.includes(searchTerm.toLowerCase()),
|
||||
).length === 0 ? (
|
||||
<div className="mt-24 col-span-2 lg:col-span-3 text-center text-gray-500 dark:text-gray-400">
|
||||
<img
|
||||
src={isDarkTheme ? NoFilesDarkIcon : NoFilesIcon}
|
||||
alt="No tools found"
|
||||
className="h-24 w-24 mx-auto mb-2"
|
||||
/>
|
||||
{t('settings.tools.noToolsFound')}
|
||||
</div>
|
||||
) : (
|
||||
userTools
|
||||
.filter((tool) =>
|
||||
tool.displayName
|
||||
.toLowerCase()
|
||||
.includes(searchTerm.toLowerCase()),
|
||||
)
|
||||
.map((tool, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="relative h-56 w-full p-6 border rounded-2xl border-silver dark:border-silver/40 flex flex-col justify-between"
|
||||
>
|
||||
<div className="w-full">
|
||||
<div className="w-full flex items-center justify-between">
|
||||
<img
|
||||
src={CogwheelIcon}
|
||||
alt="settings"
|
||||
className="h-[19px] w-[19px]"
|
||||
src={`/toolIcons/tool_${tool.name}.svg`}
|
||||
alt={`${tool.displayName} icon`}
|
||||
className="h-8 w-8"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
className="absolute top-3 right-3 cursor-pointer"
|
||||
onClick={() => handleSettingsClick(tool)}
|
||||
aria-label={t('settings.tools.configureToolAria', {
|
||||
toolName: tool.displayName,
|
||||
})}
|
||||
>
|
||||
<img
|
||||
src={CogwheelIcon}
|
||||
alt={t('settings.tools.settingsIconAlt')}
|
||||
className="h-[19px] w-[19px]"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-[9px]">
|
||||
<p className="text-sm font-semibold text-eerie-black dark:text-[#EEEEEE] leading-relaxed">
|
||||
{tool.displayName}
|
||||
</p>
|
||||
<p className="mt-1 h-16 overflow-auto text-[13px] text-gray-600 dark:text-gray-400 leading-relaxed pr-1">
|
||||
{tool.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-[9px]">
|
||||
<p className="text-sm font-semibold text-eerie-black dark:text-[#EEEEEE] leading-relaxed">
|
||||
{tool.displayName}
|
||||
</p>
|
||||
<p className="mt-1 h-16 overflow-auto text-[13px] text-gray-600 dark:text-gray-400 leading-relaxed pr-1">
|
||||
{tool.description}
|
||||
</p>
|
||||
<div className="absolute bottom-3 right-3">
|
||||
<label
|
||||
htmlFor={`toolToggle-${index}`}
|
||||
className="relative inline-block h-6 w-10 cursor-pointer rounded-full bg-gray-300 dark:bg-[#D2D5DA33]/20 transition [-webkit-tap-highlight-color:_transparent] has-[:checked]:bg-[#0C9D35CC] has-[:checked]:dark:bg-[#0C9D35CC]"
|
||||
>
|
||||
<span className="sr-only">
|
||||
{t('settings.tools.toggleToolAria', {
|
||||
toolName: tool.displayName,
|
||||
})}
|
||||
</span>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`toolToggle-${index}`}
|
||||
className="peer sr-only"
|
||||
checked={tool.status}
|
||||
onChange={() =>
|
||||
updateToolStatus(tool.id, !tool.status)
|
||||
}
|
||||
/>
|
||||
<span className="absolute inset-y-0 start-0 m-[3px] size-[18px] rounded-full bg-white transition-all peer-checked:start-4"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute bottom-3 right-3">
|
||||
<label
|
||||
htmlFor={`toolToggle-${index}`}
|
||||
className="relative inline-block h-6 w-10 cursor-pointer rounded-full bg-gray-300 dark:bg-[#D2D5DA33]/20 transition [-webkit-tap-highlight-color:_transparent] has-[:checked]:bg-[#0C9D35CC] has-[:checked]:dark:bg-[#0C9D35CC]"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
id={`toolToggle-${index}`}
|
||||
className="peer sr-only"
|
||||
checked={tool.status}
|
||||
onChange={() =>
|
||||
updateToolStatus(tool.id, !tool.status)
|
||||
}
|
||||
/>
|
||||
<span className="absolute inset-y-0 start-0 m-[3px] size-[18px] rounded-full bg-white transition-all peer-checked:start-4"></span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<AddToolModal
|
||||
message="Select a tool to set up"
|
||||
message={t('settings.tools.selectToolSetup')}
|
||||
modalState={addToolModalState}
|
||||
setModalState={setAddToolModalState}
|
||||
getUserTools={getUserTools}
|
||||
|
||||
@@ -91,8 +91,8 @@ export default function Settings() {
|
||||
case 'Widgets':
|
||||
return (
|
||||
<Widgets
|
||||
widgetScreenshot={widgetScreenshot} // Add this line
|
||||
onWidgetScreenshotChange={updateWidgetScreenshot} // Add this line
|
||||
widgetScreenshot={widgetScreenshot}
|
||||
onWidgetScreenshotChange={updateWidgetScreenshot}
|
||||
/>
|
||||
);
|
||||
case t('settings.apiKeys.label'):
|
||||
|
||||
@@ -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<number | null>();
|
||||
|
||||
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({
|
||||
<div className="mt-5 flex flex-col items-center gap-2 text-gray-2000 dark:text-bright-gray">
|
||||
<p className="text-gra text-xl tracking-[0.15px]">
|
||||
{isTraining &&
|
||||
(progress?.percentage === 100 ? 'Training completed' : title)}
|
||||
(progress?.percentage === 100
|
||||
? t('modals.uploadDoc.progress.completed')
|
||||
: title)}
|
||||
{!isTraining && title}
|
||||
</p>
|
||||
<p className="text-sm">This may take several minutes</p>
|
||||
<p className="text-sm">{t('modals.uploadDoc.progress.wait')}</p>
|
||||
<p className={`ml-5 text-xl text-red-400 ${isFailed ? '' : 'hidden'}`}>
|
||||
Over the token limit, please consider uploading smaller document
|
||||
{t('modals.uploadDoc.progress.tokenLimit')}
|
||||
</p>
|
||||
{/* <p className="mt-10 text-2xl">{progress?.percentage || 0}%</p> */}
|
||||
<ProgressBar progressPercent={progress?.percentage || 0} />
|
||||
@@ -149,7 +150,7 @@ function Upload({
|
||||
}
|
||||
|
||||
function UploadProgress() {
|
||||
return <Progress title="Upload is in progress"></Progress>;
|
||||
return <Progress title={t('modals.uploadDoc.progress.upload')}></Progress>;
|
||||
}
|
||||
|
||||
function TrainingProgress() {
|
||||
@@ -240,7 +241,7 @@ function Upload({
|
||||
}, [progress, dispatch]);
|
||||
return (
|
||||
<Progress
|
||||
title="Training is in progress"
|
||||
title={t('modals.uploadDoc.progress.training')}
|
||||
isCancellable={progress?.percentage === 100}
|
||||
isFailed={progress?.failed === true}
|
||||
isTraining={true}
|
||||
@@ -510,7 +511,7 @@ function Upload({
|
||||
<div className="flex flex-col gap-1 mt-2">
|
||||
<div>
|
||||
<Input
|
||||
placeholder="Enter client ID"
|
||||
placeholder={t('modals.uploadDoc.reddit.id')}
|
||||
type="text"
|
||||
name="client_id"
|
||||
value={redditData.client_id}
|
||||
@@ -525,7 +526,7 @@ function Upload({
|
||||
</div>
|
||||
<div>
|
||||
<Input
|
||||
placeholder="Enter client secret"
|
||||
placeholder={t('modals.uploadDoc.reddit.secret')}
|
||||
type="text"
|
||||
name="client_secret"
|
||||
value={redditData.client_secret}
|
||||
@@ -540,7 +541,7 @@ function Upload({
|
||||
</div>
|
||||
<div>
|
||||
<Input
|
||||
placeholder="Enter user agent"
|
||||
placeholder={t('modals.uploadDoc.reddit.agent')}
|
||||
type="text"
|
||||
name="user_agent"
|
||||
value={redditData.user_agent}
|
||||
@@ -555,7 +556,7 @@ function Upload({
|
||||
</div>
|
||||
<div>
|
||||
<Input
|
||||
placeholder="Enter search queries"
|
||||
placeholder={t('modals.uploadDoc.reddit.searchQueries')}
|
||||
type="text"
|
||||
name="search_queries"
|
||||
value={redditData.search_queries}
|
||||
@@ -570,7 +571,7 @@ function Upload({
|
||||
</div>
|
||||
<div>
|
||||
<Input
|
||||
placeholder="Enter number of posts"
|
||||
placeholder={t('modals.uploadDoc.reddit.numberOfPosts')}
|
||||
type="number"
|
||||
name="number_posts"
|
||||
value={redditData.number_posts}
|
||||
@@ -587,70 +588,57 @@ function Upload({
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{activeTab && (
|
||||
<div className="flex w-full justify-between flex-row-reverse">
|
||||
{activeTab === 'file' ? (
|
||||
<button
|
||||
onClick={uploadFile}
|
||||
className={`ml-2 cursor-pointer rounded-3xl bg-purple-30 text-sm text-white ${
|
||||
files.length > 0 && docName.trim().length > 0
|
||||
? 'hover:bg-[#6F3FD1]'
|
||||
: 'bg-opacity-75 text-opacity-80'
|
||||
} py-2 px-6`}
|
||||
disabled={
|
||||
(files.length === 0 || docName.trim().length === 0) &&
|
||||
activeTab === 'file'
|
||||
}
|
||||
>
|
||||
{t('modals.uploadDoc.train')}
|
||||
</button>
|
||||
) : (
|
||||
<button
|
||||
onClick={uploadRemote}
|
||||
className={`ml-2 cursor-pointer rounded-3xl bg-purple-30 py-2 px-6 text-sm text-white hover:bg-[#6F3FD1] ${
|
||||
urlName.trim().length === 0 ||
|
||||
url.trim().length === 0 ||
|
||||
(urlType.label === 'Reddit' &&
|
||||
(redditData.client_id.length === 0 ||
|
||||
redditData.client_secret.length === 0 ||
|
||||
redditData.user_agent.length === 0 ||
|
||||
redditData.search_queries.length === 0 ||
|
||||
redditData.number_posts === 0)) ||
|
||||
(urlType.label === 'GitHub' && repoUrl.trim().length === 0)
|
||||
? 'bg-opacity-80 text-opacity-80'
|
||||
: ''
|
||||
}`}
|
||||
disabled={
|
||||
urlName.trim().length === 0 ||
|
||||
url.trim().length === 0 ||
|
||||
(urlType.label === 'Reddit' &&
|
||||
(redditData.client_id.length === 0 ||
|
||||
redditData.client_secret.length === 0 ||
|
||||
redditData.user_agent.length === 0 ||
|
||||
redditData.search_queries.length === 0 ||
|
||||
redditData.number_posts === 0)) ||
|
||||
(urlType.label === 'GitHub' && repoUrl.trim().length === 0)
|
||||
}
|
||||
>
|
||||
{t('modals.uploadDoc.train')}
|
||||
</button>
|
||||
)}
|
||||
<div className="flex justify-between">
|
||||
{activeTab && (
|
||||
<button
|
||||
onClick={() => {
|
||||
setDocName('');
|
||||
setfiles([]);
|
||||
setActiveTab(null);
|
||||
}}
|
||||
className="cursor-pointer rounded-3xl px-5 py-2 text-sm font-medium hover:bg-gray-100 dark:bg-transparent dark:text-light-gray dark:hover:bg-[#767183]/50 flex items-center gap-1"
|
||||
onClick={() => setActiveTab(null)}
|
||||
className="rounded-3xl border border-purple-30 px-4 py-2 font-medium text-purple-30 hover:cursor-pointer dark:bg-purple-taupe dark:text-silver"
|
||||
>
|
||||
<img
|
||||
src={ArrowLeft}
|
||||
className="w-[10px] h-[10px] dark:filter dark:invert"
|
||||
/>
|
||||
{t('modals.uploadDoc.back')}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
)}
|
||||
<button
|
||||
onClick={() => {
|
||||
if (activeTab === 'file') {
|
||||
uploadFile();
|
||||
} else {
|
||||
uploadRemote();
|
||||
}
|
||||
}}
|
||||
disabled={
|
||||
(activeTab === 'file' && (!files.length || !docName)) ||
|
||||
(activeTab === 'remote' &&
|
||||
((urlType.label !== 'Reddit' &&
|
||||
urlType.label !== 'GitHub' &&
|
||||
(!url || !urlName)) ||
|
||||
(urlType.label === 'GitHub' && !repoUrl) ||
|
||||
(urlType.label === 'Reddit' &&
|
||||
(!redditData.client_id ||
|
||||
!redditData.client_secret ||
|
||||
!redditData.user_agent ||
|
||||
!redditData.search_queries ||
|
||||
!redditData.number_posts))))
|
||||
}
|
||||
className={`rounded-3xl px-4 py-2 font-medium ${
|
||||
(activeTab === 'file' && (!files.length || !docName)) ||
|
||||
(activeTab === 'remote' &&
|
||||
((urlType.label !== 'Reddit' &&
|
||||
urlType.label !== 'GitHub' &&
|
||||
(!url || !urlName)) ||
|
||||
(urlType.label === 'GitHub' && !repoUrl) ||
|
||||
(urlType.label === 'Reddit' &&
|
||||
(!redditData.client_id ||
|
||||
!redditData.client_secret ||
|
||||
!redditData.user_agent ||
|
||||
!redditData.search_queries ||
|
||||
!redditData.number_posts))))
|
||||
? 'cursor-not-allowed bg-gray-300 text-gray-500'
|
||||
: 'cursor-pointer bg-purple-30 text-white hover:bg-purple-40'
|
||||
}`}
|
||||
>
|
||||
{t('modals.uploadDoc.train')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user