Merge branch 'main' into tool-use

This commit is contained in:
Alex
2024-12-20 17:29:41 +00:00
committed by GitHub
84 changed files with 4317 additions and 5522 deletions

View File

@@ -24,7 +24,7 @@ jobs:
python -m pytest --cov=application --cov-report=xml
- name: Upload coverage reports to Codecov
if: github.event_name == 'pull_request' && matrix.python-version == '3.11'
uses: codecov/codecov-action@v4
uses: codecov/codecov-action@v5
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -27,9 +27,7 @@ Say goodbye to time-consuming manual searches, and let <strong><a href="https://
We're eager to provide personalized assistance when deploying your DocsGPT to a live environment.
<a href ="https://cal.com/arc53/docsgpt-demo-b2b">
<img alt="Let's chat" src="https://cal.com/book-with-cal-dark.svg" />
</a>
[Book a Meeting :wave:](https://cal.com/arc53/docsgpt-demo-b2b)
[Send Email :email:](mailto:contact@arc53.com?subject=DocsGPT%20support%2Fsolutions)
@@ -125,7 +123,7 @@ docker compose -f docker-compose-dev.yaml up -d
### Run the Backend
> [!Note]
> Make sure you have Python 3.10 or 3.11 installed.
> Make sure you have Python 3.12 installed.
1. Export required environment variables or prepare a `.env` file in the project folder:
- Copy [.env-template](https://github.com/arc53/DocsGPT/blob/main/application/.env-template) and create `.env`.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@@ -18,7 +18,7 @@ from application.error import bad_request
from application.extensions import api
from application.llm.llm_creator import LLMCreator
from application.retriever.retriever_creator import RetrieverCreator
from application.utils import check_required_fields
from application.utils import check_required_fields, limit_chat_history
logger = logging.getLogger(__name__)
@@ -118,8 +118,31 @@ def is_azure_configured():
)
def save_conversation(conversation_id, question, response, source_log_docs, llm):
if conversation_id is not None and conversation_id != "None":
def save_conversation(conversation_id, question, response, source_log_docs, llm,index=None):
if conversation_id is not None and index is not None:
conversations_collection.update_one(
{"_id": ObjectId(conversation_id), f"queries.{index}": {"$exists": True}},
{
"$set": {
f"queries.{index}.prompt": question,
f"queries.{index}.response": response,
f"queries.{index}.sources": source_log_docs,
}
}
)
##remove following queries from the array
conversations_collection.update_one(
{"_id": ObjectId(conversation_id), f"queries.{index}": {"$exists": True}},
{
"$push":{
"queries":{
"$each":[],
"$slice":index+1
}
}
}
)
elif conversation_id is not None and conversation_id != "None":
conversations_collection.update_one(
{"_id": ObjectId(conversation_id)},
{
@@ -141,17 +164,17 @@ def save_conversation(conversation_id, question, response, source_log_docs, llm)
"role": "assistant",
"content": "Summarise following conversation in no more than 3 "
"words, respond ONLY with the summary, use the same "
"language as the system \n\nUser: "
+ question
+ "\n\n"
+ "AI: "
+ response,
"language as the system",
},
{
"role": "user",
"content": "Summarise following conversation in no more than 3 words, "
"respond ONLY with the summary, use the same language as the "
"system",
"system \n\nUser: "
+ question
+ "\n\n"
+ "AI: "
+ response,
},
]
@@ -186,7 +209,7 @@ def get_prompt(prompt_id):
def complete_stream(
question, retriever, conversation_id, user_api_key, isNoneDoc=False
question, retriever, conversation_id, user_api_key, isNoneDoc=False,index=None
):
try:
@@ -217,7 +240,7 @@ def complete_stream(
)
if user_api_key is None:
conversation_id = save_conversation(
conversation_id, question, response_full, source_log_docs, llm
conversation_id, question, response_full, source_log_docs, llm,index
)
# send data.type = "end" to indicate that the stream has ended as json
data = json.dumps({"type": "id", "id": str(conversation_id)})
@@ -282,6 +305,9 @@ class Stream(Resource):
"isNoneDoc": fields.Boolean(
required=False, description="Flag indicating if no document is used"
),
"index":fields.Integer(
required=False, description="The position where query is to be updated"
),
},
)
@@ -290,23 +316,23 @@ class Stream(Resource):
def post(self):
data = request.get_json()
required_fields = ["question"]
if "index" in data:
required_fields = ["question","conversation_id"]
missing_fields = check_required_fields(data, required_fields)
if missing_fields:
return missing_fields
try:
question = data["question"]
history = data.get("history", [])
history = json.loads(history)
history = limit_chat_history(json.loads(data.get("history", [])), gpt_model=gpt_model)
conversation_id = data.get("conversation_id")
prompt_id = data.get("prompt_id", "default")
index=data.get("index",None)
chunks = int(data.get("chunks", 2))
token_limit = data.get("token_limit", settings.DEFAULT_MAX_HISTORY)
retriever_name = data.get("retriever", "classic")
if "api_key" in data:
data_key = get_data_from_api_key(data["api_key"])
chunks = int(data_key.get("chunks", 2))
@@ -343,7 +369,7 @@ class Stream(Resource):
gpt_model=gpt_model,
user_api_key=user_api_key,
)
return Response(
complete_stream(
question=question,
@@ -351,6 +377,7 @@ class Stream(Resource):
conversation_id=conversation_id,
user_api_key=user_api_key,
isNoneDoc=data.get("isNoneDoc"),
index=index,
),
mimetype="text/event-stream",
)
@@ -428,7 +455,7 @@ class Answer(Resource):
try:
question = data["question"]
history = data.get("history", [])
history = limit_chat_history(json.loads(data.get("history", [])), gpt_model=gpt_model)
conversation_id = data.get("conversation_id")
prompt_id = data.get("prompt_id", "default")
chunks = int(data.get("chunks", 2))

View File

@@ -181,10 +181,12 @@ class SubmitFeedback(Resource):
"FeedbackModel",
{
"question": fields.String(
required=True, description="The user question"
required=False, description="The user question"
),
"answer": fields.String(required=True, description="The AI answer"),
"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"),
"api_key": fields.String(description="Optional API key"),
},
)
@@ -194,23 +196,21 @@ class SubmitFeedback(Resource):
)
def post(self):
data = request.get_json()
required_fields = ["question", "answer", "feedback"]
required_fields = [ "feedback","conversation_id","question_index"]
missing_fields = check_required_fields(data, required_fields)
if missing_fields:
return missing_fields
new_doc = {
"question": data["question"],
"answer": data["answer"],
"feedback": data["feedback"],
"timestamp": datetime.datetime.now(datetime.timezone.utc),
}
if "api_key" in data:
new_doc["api_key"] = data["api_key"]
try:
feedback_collection.insert_one(new_doc)
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"]
}
}
)
except Exception as err:
return make_response(jsonify({"success": False, "error": str(err)}), 400)
@@ -253,13 +253,12 @@ class DeleteOldIndexes(Resource):
jsonify({"success": False, "message": "Missing required fields"}), 400
)
try:
doc = sources_collection.find_one(
doc = sources_collection.find_one(
{"_id": ObjectId(source_id), "user": "local"}
)
if not doc:
)
if not doc:
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"])))
else:
@@ -268,12 +267,12 @@ class DeleteOldIndexes(Resource):
)
vectorstore.delete_index()
sources_collection.delete_one({"_id": ObjectId(source_id)})
except FileNotFoundError:
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)
@@ -344,6 +343,9 @@ class UploadFile(Resource):
".json",
".xlsx",
".pptx",
".png",
".jpg",
".jpeg",
],
job_name,
final_filename,
@@ -370,6 +372,9 @@ class UploadFile(Resource):
".json",
".xlsx",
".pptx",
".png",
".jpg",
".jpeg",
],
job_name,
final_filename,
@@ -478,11 +483,22 @@ class PaginatedSources(Resource):
sort_order = request.args.get("order", "desc") # Default to 'desc'
page = int(request.args.get("page", 1)) # Default to 1
rows_per_page = int(request.args.get("rows", 10)) # Default to 10
# add .strip() to remove leading and trailing whitespaces
search_term = request.args.get(
"search", ""
).strip() # add search for filter documents
# Prepare
# Prepare query for filtering
query = {"user": user}
if search_term:
query["name"] = {
"$regex": search_term,
"$options": "i", # using case-insensitive search
}
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
sort_order = 1 if sort_order == "asc" else -1
skip = (page - 1) * rows_per_page
@@ -2082,3 +2098,4 @@ class DeleteTool(Resource):
return {"success": False, "error": str(err)}, 400
return {"success": True}, 200

View File

@@ -2,14 +2,22 @@ from celery import Celery
from application.core.settings import settings
from celery.signals import setup_logging
def make_celery(app_name=__name__):
celery = Celery(app_name, broker=settings.CELERY_BROKER_URL, backend=settings.CELERY_RESULT_BACKEND)
celery = Celery(
app_name,
broker=settings.CELERY_BROKER_URL,
backend=settings.CELERY_RESULT_BACKEND,
)
celery.conf.update(settings)
return celery
@setup_logging.connect
def config_loggers(*args, **kwargs):
from application.core.logging_config import setup_logging
setup_logging()
celery = make_celery()

View File

@@ -16,8 +16,9 @@ class Settings(BaseSettings):
MONGO_URI: str = "mongodb://localhost:27017/docsgpt"
MODEL_PATH: str = os.path.join(current_dir, "models/docsgpt-7b-f16.gguf")
DEFAULT_MAX_HISTORY: int = 150
MODEL_TOKEN_LIMITS: dict = {"gpt-3.5-turbo": 4096, "claude-2": 1e5}
MODEL_TOKEN_LIMITS: dict = {"gpt-4o-mini": 128000, "gpt-3.5-turbo": 4096, "claude-2": 1e5}
UPLOAD_FOLDER: str = "inputs"
PARSE_PDF_AS_IMAGE: bool = False
VECTOR_STORE: str = "faiss" # "faiss" or "elasticsearch" or "qdrant" or "milvus" or "lancedb"
RETRIEVERS_ENABLED: list = ["classic_rag", "duckduck_search"] # also brave_search

View File

@@ -9,35 +9,25 @@ class DocsGPTAPILLM(BaseLLM):
super().__init__(*args, **kwargs)
self.api_key = api_key
self.user_api_key = user_api_key
self.endpoint = "https://llm.docsgpt.co.uk"
self.endpoint = "https://llm.arc53.com"
def _raw_gen(self, baseself, model, messages, stream=False, *args, **kwargs):
context = messages[0]["content"]
user_question = messages[-1]["content"]
prompt = f"### Instruction \n {user_question} \n ### Context \n {context} \n ### Answer \n"
response = requests.post(
f"{self.endpoint}/answer", json={"prompt": prompt, "max_new_tokens": 30}
f"{self.endpoint}/answer", json={"messages": messages, "max_new_tokens": 30}
)
response_clean = response.json()["a"].replace("###", "")
return response_clean
def _raw_gen_stream(self, baseself, model, messages, stream=True, *args, **kwargs):
context = messages[0]["content"]
user_question = messages[-1]["content"]
prompt = f"### Instruction \n {user_question} \n ### Context \n {context} \n ### Answer \n"
# send prompt to endpoint /stream
response = requests.post(
f"{self.endpoint}/stream",
json={"prompt": prompt, "max_new_tokens": 256},
json={"messages": messages, "max_new_tokens": 256},
stream=True,
)
for line in response.iter_lines():
if line:
# data = json.loads(line)
data_str = line.decode("utf-8")
if data_str.startswith("data: "):
data = json.loads(data_str[6:])

View File

@@ -13,6 +13,7 @@ from application.parser.file.rst_parser import RstParser
from application.parser.file.tabular_parser import PandasCSVParser,ExcelParser
from application.parser.file.json_parser import JSONParser
from application.parser.file.pptx_parser import PPTXParser
from application.parser.file.image_parser import ImageParser
from application.parser.schema.base import Document
DEFAULT_FILE_EXTRACTOR: Dict[str, BaseParser] = {
@@ -27,6 +28,9 @@ DEFAULT_FILE_EXTRACTOR: Dict[str, BaseParser] = {
".mdx": MarkdownParser(),
".json":JSONParser(),
".pptx":PPTXParser(),
".png": ImageParser(),
".jpg": ImageParser(),
".jpeg": ImageParser(),
}

View File

@@ -7,7 +7,8 @@ from pathlib import Path
from typing import Dict
from application.parser.file.base_parser import BaseParser
from application.core.settings import settings
import requests
class PDFParser(BaseParser):
"""PDF parser."""
@@ -18,6 +19,15 @@ class PDFParser(BaseParser):
def parse_file(self, file: Path, errors: str = "ignore") -> str:
"""Parse file."""
if settings.PARSE_PDF_AS_IMAGE:
doc2md_service = "https://llm.arc53.com/doc2md"
# alternatively you can use local vision capable LLM
with open(file, "rb") as file_loaded:
files = {'file': file_loaded}
response = requests.post(doc2md_service, files=files)
data = response.json()["markdown"]
return data
try:
import PyPDF2
except ImportError:

View File

@@ -0,0 +1,27 @@
"""Image parser.
Contains parser for .png, .jpg, .jpeg files.
"""
from pathlib import Path
import requests
from typing import Dict, Union
from application.parser.file.base_parser import BaseParser
class ImageParser(BaseParser):
"""Image parser."""
def _init_parser(self) -> Dict:
"""Init parser."""
return {}
def parse_file(self, file: Path, errors: str = "ignore") -> Union[str, list[str]]:
doc2md_service = "https://llm.arc53.com/doc2md"
# alternatively you can use local vision capable LLM
with open(file, "rb") as file_loaded:
files = {'file': file_loaded}
response = requests.post(doc2md_service, files=files)
data = response.json()["markdown"]
return data

View File

@@ -91,6 +91,25 @@ class RstParser(BaseParser):
]
return rst_tups
def chunk_by_token_count(self, text: str, max_tokens: int = 100) -> List[str]:
"""Chunk text by token count."""
avg_token_length = 5
chunk_size = max_tokens * avg_token_length
chunks = []
for i in range(0, len(text), chunk_size):
chunk = text[i:i+chunk_size]
if i + chunk_size < len(text):
last_space = chunk.rfind(' ')
if last_space != -1:
chunk = chunk[:last_space]
chunks.append(chunk.strip())
return chunks
def remove_images(self, content: str) -> str:
pattern = r"\.\. image:: (.*)"
content = re.sub(pattern, "", content)
@@ -136,7 +155,7 @@ class RstParser(BaseParser):
return {}
def parse_tups(
self, filepath: Path, errors: str = "ignore"
self, filepath: Path, errors: str = "ignore",max_tokens: Optional[int] = 1000
) -> List[Tuple[Optional[str], str]]:
"""Parse file into tuples."""
with open(filepath, "r") as f:
@@ -156,6 +175,15 @@ class RstParser(BaseParser):
rst_tups = self.remove_whitespaces_excess(rst_tups)
if self._remove_characters_excess:
rst_tups = self.remove_characters_excess(rst_tups)
# Apply chunking if max_tokens is provided
if max_tokens is not None:
chunked_tups = []
for header, text in rst_tups:
chunks = self.chunk_by_token_count(text, max_tokens)
for idx, chunk in enumerate(chunks):
chunked_tups.append((f"{header} - Chunk {idx + 1}", chunk))
return chunked_tups
return rst_tups
def parse_file(

View File

@@ -1,66 +0,0 @@
import os
import javalang
def find_files(directory):
files_list = []
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith('.java'):
files_list.append(os.path.join(root, file))
return files_list
def extract_functions(file_path):
with open(file_path, "r") as file:
java_code = file.read()
methods = {}
tree = javalang.parse.parse(java_code)
for _, node in tree.filter(javalang.tree.MethodDeclaration):
method_name = node.name
start_line = node.position.line - 1
end_line = start_line
brace_count = 0
for line in java_code.splitlines()[start_line:]:
end_line += 1
brace_count += line.count("{") - line.count("}")
if brace_count == 0:
break
method_source_code = "\n".join(java_code.splitlines()[start_line:end_line])
methods[method_name] = method_source_code
return methods
def extract_classes(file_path):
with open(file_path, 'r') as file:
source_code = file.read()
classes = {}
tree = javalang.parse.parse(source_code)
for class_decl in tree.types:
class_name = class_decl.name
declarations = []
methods = []
for field_decl in class_decl.fields:
field_name = field_decl.declarators[0].name
field_type = field_decl.type.name
declarations.append(f"{field_type} {field_name}")
for method_decl in class_decl.methods:
methods.append(method_decl.name)
class_string = "Declarations: " + ", ".join(declarations) + "\n Method name: " + ", ".join(methods)
classes[class_name] = class_string
return classes
def extract_functions_and_classes(directory):
files = find_files(directory)
functions_dict = {}
classes_dict = {}
for file in files:
functions = extract_functions(file)
if functions:
functions_dict[file] = functions
classes = extract_classes(file)
if classes:
classes_dict[file] = classes
return functions_dict, classes_dict

View File

@@ -1,70 +0,0 @@
import os
import escodegen
import esprima
def find_files(directory):
files_list = []
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith('.js'):
files_list.append(os.path.join(root, file))
return files_list
def extract_functions(file_path):
with open(file_path, 'r') as file:
source_code = file.read()
functions = {}
tree = esprima.parseScript(source_code)
for node in tree.body:
if node.type == 'FunctionDeclaration':
func_name = node.id.name if node.id else '<anonymous>'
functions[func_name] = escodegen.generate(node)
elif node.type == 'VariableDeclaration':
for declaration in node.declarations:
if declaration.init and declaration.init.type == 'FunctionExpression':
func_name = declaration.id.name if declaration.id else '<anonymous>'
functions[func_name] = escodegen.generate(declaration.init)
elif node.type == 'ClassDeclaration':
for subnode in node.body.body:
if subnode.type == 'MethodDefinition':
func_name = subnode.key.name
functions[func_name] = escodegen.generate(subnode.value)
elif subnode.type == 'VariableDeclaration':
for declaration in subnode.declarations:
if declaration.init and declaration.init.type == 'FunctionExpression':
func_name = declaration.id.name if declaration.id else '<anonymous>'
functions[func_name] = escodegen.generate(declaration.init)
return functions
def extract_classes(file_path):
with open(file_path, 'r') as file:
source_code = file.read()
classes = {}
tree = esprima.parseScript(source_code)
for node in tree.body:
if node.type == 'ClassDeclaration':
class_name = node.id.name
function_names = []
for subnode in node.body.body:
if subnode.type == 'MethodDefinition':
function_names.append(subnode.key.name)
classes[class_name] = ", ".join(function_names)
return classes
def extract_functions_and_classes(directory):
files = find_files(directory)
functions_dict = {}
classes_dict = {}
for file in files:
functions = extract_functions(file)
if functions:
functions_dict[file] = functions
classes = extract_classes(file)
if classes:
classes_dict[file] = classes
return functions_dict, classes_dict

View File

@@ -1,121 +0,0 @@
import ast
import os
from pathlib import Path
import tiktoken
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
def find_files(directory):
files_list = []
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith('.py'):
files_list.append(os.path.join(root, file))
return files_list
def extract_functions(file_path):
with open(file_path, 'r') as file:
source_code = file.read()
functions = {}
tree = ast.parse(source_code)
for node in ast.walk(tree):
if isinstance(node, ast.FunctionDef):
func_name = node.name
func_def = ast.get_source_segment(source_code, node)
functions[func_name] = func_def
return functions
def extract_classes(file_path):
with open(file_path, 'r') as file:
source_code = file.read()
classes = {}
tree = ast.parse(source_code)
for node in ast.walk(tree):
if isinstance(node, ast.ClassDef):
class_name = node.name
function_names = []
for subnode in ast.walk(node):
if isinstance(subnode, ast.FunctionDef):
function_names.append(subnode.name)
classes[class_name] = ", ".join(function_names)
return classes
def extract_functions_and_classes(directory):
files = find_files(directory)
functions_dict = {}
classes_dict = {}
for file in files:
functions = extract_functions(file)
if functions:
functions_dict[file] = functions
classes = extract_classes(file)
if classes:
classes_dict[file] = classes
return functions_dict, classes_dict
def parse_functions(functions_dict, formats, dir):
c1 = len(functions_dict)
for i, (source, functions) in enumerate(functions_dict.items(), start=1):
print(f"Processing file {i}/{c1}")
source_w = source.replace(dir + "/", "").replace("." + formats, ".md")
subfolders = "/".join(source_w.split("/")[:-1])
Path(f"outputs/{subfolders}").mkdir(parents=True, exist_ok=True)
for j, (name, function) in enumerate(functions.items(), start=1):
print(f"Processing function {j}/{len(functions)}")
prompt = PromptTemplate(
input_variables=["code"],
template="Code: \n{code}, \nDocumentation: ",
)
llm = OpenAI(temperature=0)
response = llm(prompt.format(code=function))
mode = "a" if Path(f"outputs/{source_w}").exists() else "w"
with open(f"outputs/{source_w}", mode) as f:
f.write(
f"\n\n# Function name: {name} \n\nFunction: \n```\n{function}\n```, \nDocumentation: \n{response}")
def parse_classes(classes_dict, formats, dir):
c1 = len(classes_dict)
for i, (source, classes) in enumerate(classes_dict.items()):
print(f"Processing file {i + 1}/{c1}")
source_w = source.replace(dir + "/", "").replace("." + formats, ".md")
subfolders = "/".join(source_w.split("/")[:-1])
Path(f"outputs/{subfolders}").mkdir(parents=True, exist_ok=True)
for name, function_names in classes.items():
print(f"Processing Class {i + 1}/{c1}")
prompt = PromptTemplate(
input_variables=["class_name", "functions_names"],
template="Class name: {class_name} \nFunctions: {functions_names}, \nDocumentation: ",
)
llm = OpenAI(temperature=0)
response = llm(prompt.format(class_name=name, functions_names=function_names))
with open(f"outputs/{source_w}", "a" if Path(f"outputs/{source_w}").exists() else "w") as f:
f.write(f"\n\n# Class name: {name} \n\nFunctions: \n{function_names}, \nDocumentation: \n{response}")
def transform_to_docs(functions_dict, classes_dict, formats, dir):
docs_content = ''.join([str(key) + str(value) for key, value in functions_dict.items()])
docs_content += ''.join([str(key) + str(value) for key, value in classes_dict.items()])
num_tokens = len(tiktoken.get_encoding("cl100k_base").encode(docs_content))
total_price = ((num_tokens / 1000) * 0.02)
print(f"Number of Tokens = {num_tokens:,d}")
print(f"Approx Cost = ${total_price:,.2f}")
user_input = input("Price Okay? (Y/N)\n").lower()
if user_input == "y" or user_input == "":
if not Path("outputs").exists():
Path("outputs").mkdir()
parse_functions(functions_dict, formats, dir)
parse_classes(classes_dict, formats, dir)
print("All done!")
else:
print("The API was not called. No money was spent.")

View File

@@ -1,4 +1,4 @@
anthropic==0.34.2
anthropic==0.40.0
boto3==1.34.153
beautifulsoup4==4.12.3
celery==5.3.6
@@ -28,12 +28,12 @@ jsonschema==4.23.0
jsonschema-spec==0.2.4
jsonschema-specifications==2023.7.1
kombu==5.4.2
langchain==0.3.0
langchain-community==0.3.0
langchain-core==0.3.2
langchain==0.3.11
langchain-community==0.3.11
langchain-core==0.3.25
langchain-openai==0.2.0
langchain-text-splitters==0.3.0
langsmith==0.1.125
langsmith==0.2.3
lazy-object-proxy==1.10.0
lxml==5.3.0
markupsafe==2.1.5
@@ -73,17 +73,17 @@ referencing==0.30.2
regex==2024.9.11
requests==2.32.3
retry==0.9.2
sentence-transformers==3.0.1
sentence-transformers==3.3.1
tiktoken==0.7.0
tokenizers==0.19.1
tokenizers==0.21.0
torch==2.4.1
tqdm==4.66.5
transformers==4.44.2
transformers==4.47.0
typing-extensions==4.12.2
typing-inspect==0.9.0
tzdata==2024.2
urllib3==2.2.3
vine==5.1.0
wcwidth==0.2.13
werkzeug==3.0.4
werkzeug==3.1.3
yarl==1.11.1

View File

@@ -2,7 +2,6 @@ import json
from application.retriever.base import BaseRetriever
from application.core.settings import settings
from application.llm.llm_creator import LLMCreator
from application.utils import num_tokens_from_string
from langchain_community.tools import BraveSearch
@@ -73,15 +72,8 @@ class BraveRetSearch(BaseRetriever):
yield {"source": doc}
if len(self.chat_history) > 1:
tokens_current_history = 0
# count tokens in history
for i in self.chat_history:
if "prompt" in i and "response" in i:
tokens_batch = num_tokens_from_string(i["prompt"]) + num_tokens_from_string(
i["response"]
)
if tokens_current_history + tokens_batch < self.token_limit:
tokens_current_history += tokens_batch
messages_combine.append(
{"role": "user", "content": i["prompt"]}
)

View File

@@ -6,6 +6,7 @@ from application.utils import num_tokens_from_string
from application.vectorstore.vector_creator import VectorCreator
class ClassicRAG(BaseRetriever):
def __init__(
@@ -73,15 +74,8 @@ class ClassicRAG(BaseRetriever):
yield {"source": doc}
if len(self.chat_history) > 1:
tokens_current_history = 0
# count tokens in history
for i in self.chat_history:
if "prompt" in i and "response" in i:
tokens_batch = num_tokens_from_string(
i["prompt"]
) + num_tokens_from_string(i["response"])
if tokens_current_history + tokens_batch < self.token_limit:
tokens_current_history += tokens_batch
if "prompt" in i and "response" in i:
messages_combine.append(
{"role": "user", "content": i["prompt"]}
)
@@ -89,7 +83,6 @@ class ClassicRAG(BaseRetriever):
{"role": "system", "content": i["response"]}
)
messages_combine.append({"role": "user", "content": self.question})
# llm = LLMCreator.create_llm(
# settings.LLM_NAME, api_key=settings.API_KEY, user_api_key=self.user_api_key
# )

View File

@@ -1,7 +1,6 @@
from application.retriever.base import BaseRetriever
from application.core.settings import settings
from application.llm.llm_creator import LLMCreator
from application.utils import num_tokens_from_string
from langchain_community.tools import DuckDuckGoSearchResults
from langchain_community.utilities import DuckDuckGoSearchAPIWrapper
@@ -89,16 +88,9 @@ class DuckDuckSearch(BaseRetriever):
for doc in docs:
yield {"source": doc}
if len(self.chat_history) > 1:
tokens_current_history = 0
# count tokens in history
if len(self.chat_history) > 1:
for i in self.chat_history:
if "prompt" in i and "response" in i:
tokens_batch = num_tokens_from_string(i["prompt"]) + num_tokens_from_string(
i["response"]
)
if tokens_current_history + tokens_batch < self.token_limit:
tokens_current_history += tokens_batch
if "prompt" in i and "response" in i:
messages_combine.append(
{"role": "user", "content": i["prompt"]}
)

View File

@@ -58,3 +58,40 @@ def check_required_fields(data, required_fields):
def get_hash(data):
return hashlib.md5(data.encode()).hexdigest()
def limit_chat_history(history, max_token_limit=None, gpt_model="docsgpt"):
"""
Limits chat history based on token count.
Returns a list of messages that fit within the token limit.
"""
from application.core.settings import settings
max_token_limit = (
max_token_limit
if max_token_limit and
max_token_limit < settings.MODEL_TOKEN_LIMITS.get(
gpt_model, settings.DEFAULT_MAX_HISTORY
)
else settings.MODEL_TOKEN_LIMITS.get(
gpt_model, settings.DEFAULT_MAX_HISTORY
)
)
if not history:
return []
tokens_current_history = 0
trimmed_history = []
for message in reversed(history):
if "prompt" in message and "response" in message:
tokens_batch = num_tokens_from_string(message["prompt"]) + num_tokens_from_string(
message["response"]
)
if tokens_current_history + tokens_batch < max_token_limit:
tokens_current_history += tokens_batch
trimmed_history.insert(0, message)
else:
break
return trimmed_history

743
docs/package-lock.json generated
View File

@@ -7,8 +7,8 @@
"license": "MIT",
"dependencies": {
"@vercel/analytics": "^1.1.1",
"docsgpt": "^0.4.7",
"next": "^14.2.12",
"docsgpt-react": "^0.4.8",
"next": "^14.2.20",
"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.12",
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.12.tgz",
"integrity": "sha512-3fP29GIetdwVIfIRyLKM7KrvJaqepv+6pVodEbx0P5CaMLYBtx+7eEg8JYO5L9sveJO87z9eCReceZLi0hxO1Q=="
"version": "14.2.20",
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.20.tgz",
"integrity": "sha512-JfDpuOCB0UBKlEgEy/H6qcBSzHimn/YWjUHzKl1jMeUO+QVRdzmTTl8gFJaNO87c8DXmVKhFCtwxQ9acqB3+Pw=="
},
"node_modules/@next/swc-darwin-arm64": {
"version": "14.2.12",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.12.tgz",
"integrity": "sha512-crHJ9UoinXeFbHYNok6VZqjKnd8rTd7K3Z2zpyzF1ch7vVNKmhjv/V7EHxep3ILoN8JB9AdRn/EtVVyG9AkCXw==",
"version": "14.2.20",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.20.tgz",
"integrity": "sha512-WDfq7bmROa5cIlk6ZNonNdVhKmbCv38XteVFYsxea1vDJt3SnYGgxLGMTXQNfs5OkFvAhmfKKrwe7Y0Hs+rWOg==",
"cpu": [
"arm64"
],
@@ -951,9 +951,9 @@
}
},
"node_modules/@next/swc-darwin-x64": {
"version": "14.2.12",
"resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.12.tgz",
"integrity": "sha512-JbEaGbWq18BuNBO+lCtKfxl563Uw9oy2TodnN2ioX00u7V1uzrsSUcg3Ep9ce+P0Z9es+JmsvL2/rLphz+Frcw==",
"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==",
"cpu": [
"x64"
],
@@ -966,9 +966,9 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
"version": "14.2.12",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.12.tgz",
"integrity": "sha512-qBy7OiXOqZrdp88QEl2H4fWalMGnSCrr1agT/AVDndlyw2YJQA89f3ttR/AkEIP9EkBXXeGl6cC72/EZT5r6rw==",
"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==",
"cpu": [
"arm64"
],
@@ -981,9 +981,9 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
"version": "14.2.12",
"resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.12.tgz",
"integrity": "sha512-EfD9L7o9biaQxjwP1uWXnk3vYZi64NVcKUN83hpVkKocB7ogJfyH2r7o1pPnMtir6gHZiGCeHKagJ0yrNSLNHw==",
"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==",
"cpu": [
"arm64"
],
@@ -996,9 +996,9 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
"version": "14.2.12",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.12.tgz",
"integrity": "sha512-iQ+n2pxklJew9IpE47hE/VgjmljlHqtcD5UhZVeHICTPbLyrgPehaKf2wLRNjYH75udroBNCgrSSVSVpAbNoYw==",
"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==",
"cpu": [
"x64"
],
@@ -1011,9 +1011,9 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
"version": "14.2.12",
"resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.12.tgz",
"integrity": "sha512-rFkUkNwcQ0ODn7cxvcVdpHlcOpYxMeyMfkJuzaT74xjAa5v4fxP4xDk5OoYmPi8QNLDs3UgZPMSBmpBuv9zKWA==",
"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==",
"cpu": [
"x64"
],
@@ -1026,9 +1026,9 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
"version": "14.2.12",
"resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.12.tgz",
"integrity": "sha512-PQFYUvwtHs/u0K85SG4sAdDXYIPXpETf9mcEjWc0R4JmjgMKSDwIU/qfZdavtP6MPNiMjuKGXHCtyhR/M5zo8g==",
"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==",
"cpu": [
"arm64"
],
@@ -1041,9 +1041,9 @@
}
},
"node_modules/@next/swc-win32-ia32-msvc": {
"version": "14.2.12",
"resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.12.tgz",
"integrity": "sha512-FAj2hMlcbeCV546eU2tEv41dcJb4NeqFlSXU/xL/0ehXywHnNpaYajOUvn3P8wru5WyQe6cTZ8fvckj/2XN4Vw==",
"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==",
"cpu": [
"ia32"
],
@@ -1056,9 +1056,9 @@
}
},
"node_modules/@next/swc-win32-x64-msvc": {
"version": "14.2.12",
"resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.12.tgz",
"integrity": "sha512-yu8QvV53sBzoIVRHsxCHqeuS8jYq6Lrmdh0briivuh+Brsp6xjg80MAozUsBTAV9KNmY08KlX0KYTWz1lbPzEg==",
"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==",
"cpu": [
"x64"
],
@@ -1170,6 +1170,407 @@
"node": ">=8"
}
},
"node_modules/@parcel/core": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/@parcel/core/-/core-2.13.2.tgz",
"integrity": "sha512-1zC5Au4z9or5XyP6ipfvJqHktuB0jD7WuxMcV1CWAZGARHKylLe+0ccl+Wx7HN5O+xAvfCDtTlKrATY8qyrIyw==",
"peer": true,
"dependencies": {
"@mischnic/json-sourcemap": "^0.1.0",
"@parcel/cache": "2.13.2",
"@parcel/diagnostic": "2.13.2",
"@parcel/events": "2.13.2",
"@parcel/feature-flags": "2.13.2",
"@parcel/fs": "2.13.2",
"@parcel/graph": "3.3.2",
"@parcel/logger": "2.13.2",
"@parcel/package-manager": "2.13.2",
"@parcel/plugin": "2.13.2",
"@parcel/profiler": "2.13.2",
"@parcel/rust": "2.13.2",
"@parcel/source-map": "^2.1.1",
"@parcel/types": "2.13.2",
"@parcel/utils": "2.13.2",
"@parcel/workers": "2.13.2",
"base-x": "^3.0.8",
"browserslist": "^4.6.6",
"clone": "^2.1.1",
"dotenv": "^16.4.5",
"dotenv-expand": "^11.0.6",
"json5": "^2.2.0",
"msgpackr": "^1.9.9",
"nullthrows": "^1.1.1",
"semver": "^7.5.2"
},
"engines": {
"node": ">= 16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/core/node_modules/@parcel/cache": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/@parcel/cache/-/cache-2.13.2.tgz",
"integrity": "sha512-Y0nWlCMWDSp1lxiPI5zCWTGD0InnVZ+IfqeyLWmROAqValYyd0QZCvnSljKJ144jWTr0jXxDveir+DVF8sAYaA==",
"peer": true,
"dependencies": {
"@parcel/fs": "2.13.2",
"@parcel/logger": "2.13.2",
"@parcel/utils": "2.13.2",
"lmdb": "2.8.5"
},
"engines": {
"node": ">= 16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"peerDependencies": {
"@parcel/core": "^2.13.2"
}
},
"node_modules/@parcel/core/node_modules/@parcel/codeframe": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/@parcel/codeframe/-/codeframe-2.13.2.tgz",
"integrity": "sha512-qFMiS14orb6QSQj5/J/QN+gJElUfedVAKBTNkp9QB4i8ObdLHDqHRUzFb55ZQJI3G4vsxOOWAOUXGirtLwrxGQ==",
"peer": true,
"dependencies": {
"chalk": "^4.1.2"
},
"engines": {
"node": ">= 16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/core/node_modules/@parcel/diagnostic": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/@parcel/diagnostic/-/diagnostic-2.13.2.tgz",
"integrity": "sha512-6Au0JEJ5SY2gYrY0/m0i0sTuqTvK0k2E9azhBJR+zzCREbUxLiDdLZ+vXAfLW7t/kPAcWtdNU0Bj7pnZcMiMXg==",
"peer": true,
"dependencies": {
"@mischnic/json-sourcemap": "^0.1.0",
"nullthrows": "^1.1.1"
},
"engines": {
"node": ">= 16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/core/node_modules/@parcel/events": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/@parcel/events/-/events-2.13.2.tgz",
"integrity": "sha512-BVB9hW1RGh/tMaDHfpa+uIgz5PMULorCnjmWr/KvrlhdUSUQoaPYfRcTDYrKhoKuNIKsWSnTGvXrxE53L5qo0w==",
"peer": true,
"engines": {
"node": ">= 16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/core/node_modules/@parcel/fs": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/@parcel/fs/-/fs-2.13.2.tgz",
"integrity": "sha512-bdeIMuAXhMnROvqV55JWRUmjD438/T7h3r3NsFnkq+Mp4z2nuAn0STxbqDNxIgTMJHNunSDzncqRNMT7xJCe8A==",
"peer": true,
"dependencies": {
"@parcel/feature-flags": "2.13.2",
"@parcel/rust": "2.13.2",
"@parcel/types-internal": "2.13.2",
"@parcel/utils": "2.13.2",
"@parcel/watcher": "^2.0.7",
"@parcel/workers": "2.13.2"
},
"engines": {
"node": ">= 16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"peerDependencies": {
"@parcel/core": "^2.13.2"
}
},
"node_modules/@parcel/core/node_modules/@parcel/logger": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/@parcel/logger/-/logger-2.13.2.tgz",
"integrity": "sha512-SFVABAMqaT9jIDn4maPgaQQauPDz8fpoKUGEuLF44Q0aQFbBUy7vX7KYs/EvYSWZo4VyJcUDHvIInBlepA0/ZQ==",
"peer": true,
"dependencies": {
"@parcel/diagnostic": "2.13.2",
"@parcel/events": "2.13.2"
},
"engines": {
"node": ">= 16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/core/node_modules/@parcel/markdown-ansi": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/@parcel/markdown-ansi/-/markdown-ansi-2.13.2.tgz",
"integrity": "sha512-MIEoetfT/snk1GqWzBI3AhifV257i2xke9dvyQl14PPiMl+TlVhwnbQyA09WJBvDor+MuxZypHL7xoFdW8ff3A==",
"peer": true,
"dependencies": {
"chalk": "^4.1.2"
},
"engines": {
"node": ">= 16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/core/node_modules/@parcel/node-resolver-core": {
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/@parcel/node-resolver-core/-/node-resolver-core-3.4.2.tgz",
"integrity": "sha512-SwnKLcZRG1VdB5JeM/Ax5VMWWh2QfXufmMQCKKx0/Kk41nUpie+aIZKj3LH6Z/fJsnKig/vXpeWoxGhmG523qg==",
"peer": true,
"dependencies": {
"@mischnic/json-sourcemap": "^0.1.0",
"@parcel/diagnostic": "2.13.2",
"@parcel/fs": "2.13.2",
"@parcel/rust": "2.13.2",
"@parcel/utils": "2.13.2",
"nullthrows": "^1.1.1",
"semver": "^7.5.2"
},
"engines": {
"node": ">= 16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/core/node_modules/@parcel/package-manager": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/@parcel/package-manager/-/package-manager-2.13.2.tgz",
"integrity": "sha512-6HjfbdJUjHyNKzYB7GSYnOCtLwqCGW7yT95GlnnTKyFffvXYsqvBSyepMuPRlbX0mFUm4S9l2DH3OVZrk108AA==",
"peer": true,
"dependencies": {
"@parcel/diagnostic": "2.13.2",
"@parcel/fs": "2.13.2",
"@parcel/logger": "2.13.2",
"@parcel/node-resolver-core": "3.4.2",
"@parcel/types": "2.13.2",
"@parcel/utils": "2.13.2",
"@parcel/workers": "2.13.2",
"@swc/core": "^1.7.26",
"semver": "^7.5.2"
},
"engines": {
"node": ">= 16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"peerDependencies": {
"@parcel/core": "^2.13.2"
}
},
"node_modules/@parcel/core/node_modules/@parcel/plugin": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/@parcel/plugin/-/plugin-2.13.2.tgz",
"integrity": "sha512-Q+RIENS1B185yLPhrGdzBK1oJrZmh/RXrYMnzJs78Tog8SpihjeNBNR6z4PT85o2F+Gy2y1S9A26fpiGq161qQ==",
"peer": true,
"dependencies": {
"@parcel/types": "2.13.2"
},
"engines": {
"node": ">= 16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/core/node_modules/@parcel/profiler": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/@parcel/profiler/-/profiler-2.13.2.tgz",
"integrity": "sha512-fur6Oq2HkX6AiM8rtqmDvldH5JWz0sqXA1ylz8cE3XOiDZIuvCulZmQ+hH+4odaNH6QocI1MwfV+GDh3HlQoCA==",
"peer": true,
"dependencies": {
"@parcel/diagnostic": "2.13.2",
"@parcel/events": "2.13.2",
"@parcel/types-internal": "2.13.2",
"chrome-trace-event": "^1.0.2"
},
"engines": {
"node": ">= 16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/core/node_modules/@parcel/rust": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/@parcel/rust/-/rust-2.13.2.tgz",
"integrity": "sha512-XFIewSwxkrDYOnnSP/XZ1LDLdXTs7L9CjQUWtl46Vir5Pq/rinemwLJeKGIwKLHy7fhUZQjYxquH6fBL+AY8DA==",
"peer": true,
"engines": {
"node": ">= 16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/core/node_modules/@parcel/types": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/@parcel/types/-/types-2.13.2.tgz",
"integrity": "sha512-6ixqjk2pjKELn4sQ/jdvpbCVTeH6xXQTdotkN8Wzk68F2K2MtSPIRAEocumlexScfffbRQplr2MdIf1JJWLogA==",
"peer": true,
"dependencies": {
"@parcel/types-internal": "2.13.2",
"@parcel/workers": "2.13.2"
}
},
"node_modules/@parcel/core/node_modules/@parcel/utils": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/@parcel/utils/-/utils-2.13.2.tgz",
"integrity": "sha512-BkFtRo5xenmonwnBy+X4sVbHIRrx+ZHMPpS/6hFqyTvoUUFq2yTFQnfRGVVOOvscVUxpGom+kewnrTG3HHbZoA==",
"peer": true,
"dependencies": {
"@parcel/codeframe": "2.13.2",
"@parcel/diagnostic": "2.13.2",
"@parcel/logger": "2.13.2",
"@parcel/markdown-ansi": "2.13.2",
"@parcel/rust": "2.13.2",
"@parcel/source-map": "^2.1.1",
"chalk": "^4.1.2",
"nullthrows": "^1.1.1"
},
"engines": {
"node": ">= 16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/core/node_modules/@parcel/workers": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/@parcel/workers/-/workers-2.13.2.tgz",
"integrity": "sha512-P78BpH0yTT9KK09wgK4eabtlb5OlcWAmZebOToN5UYuwWEylKt0gWZx1+d+LPQupvK84/iZ+AutDScsATjgUMw==",
"peer": true,
"dependencies": {
"@parcel/diagnostic": "2.13.2",
"@parcel/logger": "2.13.2",
"@parcel/profiler": "2.13.2",
"@parcel/types-internal": "2.13.2",
"@parcel/utils": "2.13.2",
"nullthrows": "^1.1.1"
},
"engines": {
"node": ">= 16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"peerDependencies": {
"@parcel/core": "^2.13.2"
}
},
"node_modules/@parcel/core/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"peer": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@parcel/core/node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"peer": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/@parcel/core/node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"peer": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/@parcel/core/node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"peer": true
},
"node_modules/@parcel/core/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"peer": true,
"engines": {
"node": ">=8"
}
},
"node_modules/@parcel/core/node_modules/semver": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
"integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
"peer": true,
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@parcel/core/node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"peer": true,
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@parcel/diagnostic": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/@parcel/diagnostic/-/diagnostic-2.12.0.tgz",
@@ -1198,6 +1599,19 @@
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/feature-flags": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/@parcel/feature-flags/-/feature-flags-2.13.2.tgz",
"integrity": "sha512-cCwDAKD4Er24EkuQ+loVZXSURpM0gAGRsLJVoBtFiCSbB3nmIJJ6FLRwSBI/5OsOUExiUXDvSpfUCA5ldGTzbw==",
"peer": true,
"engines": {
"node": ">= 16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/fs": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/@parcel/fs/-/fs-2.12.0.tgz",
@@ -1220,6 +1634,23 @@
"@parcel/core": "^2.12.0"
}
},
"node_modules/@parcel/graph": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@parcel/graph/-/graph-3.3.2.tgz",
"integrity": "sha512-aAysQLRr8SOonSHWqdKHMJzfcrDFXKK8IYZEurlOzosiSgZXrAK7q8b8JcaJ4r84/jlvQYNYneNZeFQxKjHXkA==",
"peer": true,
"dependencies": {
"@parcel/feature-flags": "2.13.2",
"nullthrows": "^1.1.1"
},
"engines": {
"node": ">= 16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/logger": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/@parcel/logger/-/logger-2.12.0.tgz",
@@ -1569,6 +2000,35 @@
"utility-types": "^3.10.0"
}
},
"node_modules/@parcel/types-internal": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/@parcel/types-internal/-/types-internal-2.13.2.tgz",
"integrity": "sha512-j0zb3WNM8O/+d8CArll7/4w4AyBED3Jbo32/unz89EPVN0VklmgBrRCAI5QXDKuJAGdAZSL5/a8bNYbwl7/Wxw==",
"peer": true,
"dependencies": {
"@parcel/diagnostic": "2.13.2",
"@parcel/feature-flags": "2.13.2",
"@parcel/source-map": "^2.1.1",
"utility-types": "^3.10.0"
}
},
"node_modules/@parcel/types-internal/node_modules/@parcel/diagnostic": {
"version": "2.13.2",
"resolved": "https://registry.npmjs.org/@parcel/diagnostic/-/diagnostic-2.13.2.tgz",
"integrity": "sha512-6Au0JEJ5SY2gYrY0/m0i0sTuqTvK0k2E9azhBJR+zzCREbUxLiDdLZ+vXAfLW7t/kPAcWtdNU0Bj7pnZcMiMXg==",
"peer": true,
"dependencies": {
"@mischnic/json-sourcemap": "^0.1.0",
"nullthrows": "^1.1.1"
},
"engines": {
"node": ">= 16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/utils": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/@parcel/utils/-/utils-2.12.0.tgz",
@@ -2279,13 +2739,13 @@
}
},
"node_modules/@swc/core": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.4.5.tgz",
"integrity": "sha512-4/JGkG4b1Z/QwCGgx+Ub46MlzrsZvBk5JSkxm9PcZ4bSX81c+4Y94Xm3iLp5Ka8NxzS5rD4mJSpcYuN3Tw0ceg==",
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.10.1.tgz",
"integrity": "sha512-rQ4dS6GAdmtzKiCRt3LFVxl37FaY1cgL9kSUTnhQ2xc3fmHOd7jdJK/V4pSZMG1ruGTd0bsi34O2R0Olg9Zo/w==",
"hasInstallScript": true,
"dependencies": {
"@swc/counter": "^0.1.2",
"@swc/types": "^0.1.5"
"@swc/counter": "^0.1.3",
"@swc/types": "^0.1.17"
},
"engines": {
"node": ">=10"
@@ -2295,19 +2755,19 @@
"url": "https://opencollective.com/swc"
},
"optionalDependencies": {
"@swc/core-darwin-arm64": "1.4.5",
"@swc/core-darwin-x64": "1.4.5",
"@swc/core-linux-arm-gnueabihf": "1.4.5",
"@swc/core-linux-arm64-gnu": "1.4.5",
"@swc/core-linux-arm64-musl": "1.4.5",
"@swc/core-linux-x64-gnu": "1.4.5",
"@swc/core-linux-x64-musl": "1.4.5",
"@swc/core-win32-arm64-msvc": "1.4.5",
"@swc/core-win32-ia32-msvc": "1.4.5",
"@swc/core-win32-x64-msvc": "1.4.5"
"@swc/core-darwin-arm64": "1.10.1",
"@swc/core-darwin-x64": "1.10.1",
"@swc/core-linux-arm-gnueabihf": "1.10.1",
"@swc/core-linux-arm64-gnu": "1.10.1",
"@swc/core-linux-arm64-musl": "1.10.1",
"@swc/core-linux-x64-gnu": "1.10.1",
"@swc/core-linux-x64-musl": "1.10.1",
"@swc/core-win32-arm64-msvc": "1.10.1",
"@swc/core-win32-ia32-msvc": "1.10.1",
"@swc/core-win32-x64-msvc": "1.10.1"
},
"peerDependencies": {
"@swc/helpers": "^0.5.0"
"@swc/helpers": "*"
},
"peerDependenciesMeta": {
"@swc/helpers": {
@@ -2316,9 +2776,9 @@
}
},
"node_modules/@swc/core-darwin-arm64": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.4.5.tgz",
"integrity": "sha512-toMSkbByHNfGXESyY1aiq5L3KutgijrNWB/THgdHIA1aIbwtrgMdFQfxpSE+INuuvWYi/Fxarv86EnU7ewbI0Q==",
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.10.1.tgz",
"integrity": "sha512-NyELPp8EsVZtxH/mEqvzSyWpfPJ1lugpTQcSlMduZLj1EASLO4sC8wt8hmL1aizRlsbjCX+r0PyL+l0xQ64/6Q==",
"cpu": [
"arm64"
],
@@ -2331,9 +2791,9 @@
}
},
"node_modules/@swc/core-darwin-x64": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.4.5.tgz",
"integrity": "sha512-LN8cbnmb4Gav8UcbBc+L/DEthmzCWZz22rQr6fIEHMN+f0d71fuKnV0ca0hoKbpZn33dlzUmXQE53HRjlRUQbw==",
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.10.1.tgz",
"integrity": "sha512-L4BNt1fdQ5ZZhAk5qoDfUnXRabDOXKnXBxMDJ+PWLSxOGBbWE6aJTnu4zbGjJvtot0KM46m2LPAPY8ttknqaZA==",
"cpu": [
"x64"
],
@@ -2346,9 +2806,9 @@
}
},
"node_modules/@swc/core-linux-arm-gnueabihf": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.4.5.tgz",
"integrity": "sha512-suRFkhBWmOQxlM4frpos1uqjmHfaEI8FuJ0LL5+yRE7IunNDeQJBKujGZt6taeuxo1KqC0N0Ajr8IluN2wrKpA==",
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.10.1.tgz",
"integrity": "sha512-Y1u9OqCHgvVp2tYQAJ7hcU9qO5brDMIrA5R31rwWQIAKDkJKtv3IlTHF0hrbWk1wPR0ZdngkQSJZple7G+Grvw==",
"cpu": [
"arm"
],
@@ -2361,9 +2821,9 @@
}
},
"node_modules/@swc/core-linux-arm64-gnu": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.4.5.tgz",
"integrity": "sha512-mLKxasQArDGmR6k9c0tkPVUdoo8VfUecocMG1Mx9NYvpidJNaZ3xq9nYM77v7uq1fQqrs/59DM1fJTNRWvv/UQ==",
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.10.1.tgz",
"integrity": "sha512-tNQHO/UKdtnqjc7o04iRXng1wTUXPgVd8Y6LI4qIbHVoVPwksZydISjMcilKNLKIwOoUQAkxyJ16SlOAeADzhQ==",
"cpu": [
"arm64"
],
@@ -2376,9 +2836,9 @@
}
},
"node_modules/@swc/core-linux-arm64-musl": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.4.5.tgz",
"integrity": "sha512-pgKuyRP7S29U/HMDTx+x8dFcklWxwB9cHFNCNWSE6bS4vHR93jc4quwPX9OEQX5CVHxm+c8+xof043I4OGkAXw==",
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.10.1.tgz",
"integrity": "sha512-x0L2Pd9weQ6n8dI1z1Isq00VHFvpBClwQJvrt3NHzmR+1wCT/gcYl1tp9P5xHh3ldM8Cn4UjWCw+7PaUgg8FcQ==",
"cpu": [
"arm64"
],
@@ -2391,9 +2851,9 @@
}
},
"node_modules/@swc/core-linux-x64-gnu": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.4.5.tgz",
"integrity": "sha512-srR+YN86Oerzoghd0DPCzTbTp08feeJPSr9kkNdmtQWENOa4l/9cJV3+XY6vviw0sEjezPmYnc3SwRxJRaxvEw==",
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.10.1.tgz",
"integrity": "sha512-yyYEwQcObV3AUsC79rSzN9z6kiWxKAVJ6Ntwq2N9YoZqSPYph+4/Am5fM1xEQYf/kb99csj0FgOelomJSobxQA==",
"cpu": [
"x64"
],
@@ -2406,9 +2866,9 @@
}
},
"node_modules/@swc/core-linux-x64-musl": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.4.5.tgz",
"integrity": "sha512-aSf41LZtDeG5VXI4RCnzcu0UInPyNm3ip8Kw+sCK+sSqW9o7DgBkyqqbip3RZq84fNUHBQQQQdKXetltsyRRqw==",
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.10.1.tgz",
"integrity": "sha512-tcaS43Ydd7Fk7sW5ROpaf2Kq1zR+sI5K0RM+0qYLYYurvsJruj3GhBCaiN3gkzd8m/8wkqNqtVklWaQYSDsyqA==",
"cpu": [
"x64"
],
@@ -2421,9 +2881,9 @@
}
},
"node_modules/@swc/core-win32-arm64-msvc": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.4.5.tgz",
"integrity": "sha512-vU3k8JwRUlTkJMfJQY9E4VvLrsIFOpfhnvbuXB84Amo1cJsz+bYQcC6RSvY7qpaDzDKFdUGbJco4uZTRoRf7Mg==",
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.10.1.tgz",
"integrity": "sha512-D3Qo1voA7AkbOzQ2UGuKNHfYGKL6eejN8VWOoQYtGHHQi1p5KK/Q7V1ku55oxXBsj79Ny5FRMqiRJpVGad7bjQ==",
"cpu": [
"arm64"
],
@@ -2436,9 +2896,9 @@
}
},
"node_modules/@swc/core-win32-ia32-msvc": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.4.5.tgz",
"integrity": "sha512-856YRh3frRK2XbrSjDOFBgoAqWJLNRkaEtfGzXfeEoyJlOz0BFsSJHxKlHAFkxRfHe2li9DJRUQFTEhXn4OUWw==",
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.10.1.tgz",
"integrity": "sha512-WalYdFoU3454Og+sDKHM1MrjvxUGwA2oralknXkXL8S0I/8RkWZOB++p3pLaGbTvOO++T+6znFbQdR8KRaa7DA==",
"cpu": [
"ia32"
],
@@ -2451,9 +2911,9 @@
}
},
"node_modules/@swc/core-win32-x64-msvc": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.4.5.tgz",
"integrity": "sha512-j1+kV7jmWY1+NbXAvxAEW165781yLXVZKLcoXIZKmw18EatqMF6w8acg1gDG8C+Iw5aWLkRZVS4pijSh7+DtCQ==",
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.10.1.tgz",
"integrity": "sha512-JWobfQDbTnoqaIwPKQ3DVSywihVXlQMbDuwik/dDWlj33A8oEHcjPOGs4OqcA3RHv24i+lfCQpM3Mn4FAMfacA==",
"cpu": [
"x64"
],
@@ -2480,9 +2940,12 @@
}
},
"node_modules/@swc/types": {
"version": "0.1.5",
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.5.tgz",
"integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw=="
"version": "0.1.17",
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.17.tgz",
"integrity": "sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==",
"dependencies": {
"@swc/counter": "^0.1.3"
}
},
"node_modules/@theguild/remark-mermaid": {
"version": "0.0.5",
@@ -2723,6 +3186,15 @@
"url": "https://github.com/sponsors/wooorm"
}
},
"node_modules/base-x": {
"version": "3.0.10",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.10.tgz",
"integrity": "sha512-7d0s06rR9rYaIWHkpfLIFICM/tkSVdoPC9qYAQRpxn9DdKNWNsKC0uk++akckyLq16Tx2WIinnZ6WRriAt6njQ==",
"peer": true,
"dependencies": {
"safe-buffer": "^5.0.1"
}
},
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
@@ -2937,6 +3409,15 @@
"node": ">=4"
}
},
"node_modules/clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
"peer": true,
"engines": {
"node": ">=0.8"
}
},
"node_modules/clsx": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz",
@@ -3575,10 +4056,10 @@
"node": ">=0.3.1"
}
},
"node_modules/docsgpt": {
"version": "0.4.7",
"resolved": "https://registry.npmjs.org/docsgpt/-/docsgpt-0.4.7.tgz",
"integrity": "sha512-4YZzLZo6ybudFrJVUQflDFeWzFiTATRWB9myrGSpLigyuMMzax1ZAY2xFallZLuEG9VVm0mOgkx3ssWHLrXWkQ==",
"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",
"dependencies": {
"@babel/plugin-transform-flow-strip-types": "^7.23.3",
@@ -3665,6 +4146,33 @@
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/dotenv": {
"version": "16.4.7",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
"peer": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dotenv-expand": {
"version": "11.0.7",
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz",
"integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==",
"peer": true,
"dependencies": {
"dotenv": "^16.4.5"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/electron-to-chromium": {
"version": "1.4.693",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.693.tgz",
@@ -6234,9 +6742,9 @@
}
},
"node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"funding": [
{
"type": "github",
@@ -6251,11 +6759,11 @@
}
},
"node_modules/next": {
"version": "14.2.12",
"resolved": "https://registry.npmjs.org/next/-/next-14.2.12.tgz",
"integrity": "sha512-cDOtUSIeoOvt1skKNihdExWMTybx3exnvbFbb9ecZDIxlvIbREQzt9A5Km3Zn3PfU+IFjyYGsHS+lN9VInAGKA==",
"version": "14.2.20",
"resolved": "https://registry.npmjs.org/next/-/next-14.2.20.tgz",
"integrity": "sha512-yPvIiWsiyVYqJlSQxwmzMIReXn5HxFNq4+tlVQ812N1FbvhmE+fDpIAD7bcS2mGYQwPJ5vAsQouyme2eKsxaug==",
"dependencies": {
"@next/env": "14.2.12",
"@next/env": "14.2.20",
"@swc/helpers": "0.5.5",
"busboy": "1.6.0",
"caniuse-lite": "^1.0.30001579",
@@ -6270,15 +6778,15 @@
"node": ">=18.17.0"
},
"optionalDependencies": {
"@next/swc-darwin-arm64": "14.2.12",
"@next/swc-darwin-x64": "14.2.12",
"@next/swc-linux-arm64-gnu": "14.2.12",
"@next/swc-linux-arm64-musl": "14.2.12",
"@next/swc-linux-x64-gnu": "14.2.12",
"@next/swc-linux-x64-musl": "14.2.12",
"@next/swc-win32-arm64-msvc": "14.2.12",
"@next/swc-win32-ia32-msvc": "14.2.12",
"@next/swc-win32-x64-msvc": "14.2.12"
"@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"
},
"peerDependencies": {
"@opentelemetry/api": "^1.1.0",
@@ -9598,6 +10106,26 @@
"node": ">=6"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"peer": true
},
"node_modules/safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -9942,6 +10470,19 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/typescript": {
"version": "5.7.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz",
"integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==",
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/uc.micro": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz",

View File

@@ -7,8 +7,8 @@
"license": "MIT",
"dependencies": {
"@vercel/analytics": "^1.1.1",
"docsgpt": "^0.4.7",
"next": "^14.2.12",
"docsgpt-react": "^0.4.8",
"next": "^14.2.20",
"nextra": "^2.13.2",
"nextra-theme-docs": "^2.13.2",
"react": "^18.2.0",

View File

@@ -29,18 +29,30 @@ Now, you can use the widget in your component like this :
buttonBg = "#222327"
/>
```
To tailor the widget to your needs, you can configure the following props in your component:
1. `apiHost` — The URL of your DocsGPT API.
2. `theme` — Allows to select your specific theme (dark or light).
3. `apiKey` — Usually, it's empty.
4. `avatar`: Specifies the URL of the avatar or image representing the chatbot.
5. `title`: Sets the title text displayed in the chatbot interface.
6. `description`: Provides a brief description of the chatbot's purpose or functionality.
7. `heroTitle`: Displays a welcome title when users interact with the chatbot.
8. `heroDescription`: Provide additional introductory text or information about the chatbot's capabilities.
9. `buttonIcon`: Specifies the url of the icon image for the widget.
10. `buttonBg`: Allows to specify the Background color of the widget.
11. `size`: Sets the size of the widget ( small, medium).
### Props Table for DocsGPT Widget
| **Prop** | **Type** | **Default Value** | **Description** |
|--------------------|------------------|-------------------------------------------------------------|-----------------------------------------------------------------------------------------------------|
| **`apiHost`** | `string` | `"https://gptcloud.arc53.com"` | The URL of your DocsGPT API for vector search and chatbot queries. |
| **`apiKey`** | `string` | `""` | Your API key for authentication. Can be left empty if authentication is not required. |
| **`avatar`** | `string` | `"https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png"` | Specifies the URL of the avatar or image representing the chatbot. |
| **`title`** | `string` | `"Get AI assistance"` | Sets the title text displayed in the chatbot interface. |
| **`description`** | `string` | `"DocsGPT's AI Chatbot is here to help"` | Provides a brief description of the chatbot's purpose or functionality. |
| **`heroTitle`** | `string` | `"Welcome to DocsGPT !"` | Displays a welcome title when users interact with the chatbot. |
| **`heroDescription`** | `string` | `"This chatbot is built with DocsGPT and utilises GenAI, please review important information using sources."` | Provides additional introductory text or information about the chatbot's capabilities. |
| **`theme`** | `"dark" \| "light"` | `"dark"` | Allows you to select the theme for the chatbot interface. Accepts `"dark"` or `"light"`. |
| **`buttonIcon`** | `string` | `"https://your-icon"` | Specifies the URL of the icon image for the widget's launch button. |
| **`buttonBg`** | `string` | `"#222327"` | Sets the background color of the widget's launch button. |
| **`size`** | `"small" \| "medium"` | `"medium"` | Sets the size of the widget. Options are `"small"` or `"medium"`. |
---
### Notes
- **Customizing Props:** All properties can be overridden when embedding the widget. For example, you can provide a unique avatar, title, or color scheme to better align with your brand.
- **Default Theme:** The widget defaults to the dark theme unless explicitly set to `"light"`.
- **API Key:** If the `apiKey` is not required for your application, leave it empty.
This table provides a clear overview of the customization options available for tailoring the DocsGPT widget to fit your application.
### How to use DocsGPTWidget with [Nextra](https://nextra.site/) (Next.js + MDX)
@@ -121,5 +133,80 @@ To link the widget to your api and your documents you can pass parameters to the
</html>
```
# SearchBar
The `SearchBar` component is an interactive search bar designed to provide search results based on **vector similarity search**. It also includes the capability to open the AI Chatbot, enabling users to query.
---
### Importing the Component
```tsx
import { SearchBar } from "docsgpt-react";
```
---
### Usage Example
```tsx
<SearchBar
apiKey="your-api-key"
apiHost="https://gptcloud.arc53.com"
theme="light"
placeholder="Search or Ask AI..."
width="300px"
/>
```
---
## HTML embedding for Search bar
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SearchBar Embedding</title>
<script src="https://unpkg.com/docsgpt/dist/modern/main.js"></script> <!-- The bundled JavaScript file -->
</head>
<body>
<!-- Element where the SearchBar will render -->
<div id="search-bar-container"></div>
<script>
// Render the SearchBar into the specified element
renderSearchBar('search-bar-container', {
apiKey: 'your-api-key-here',
apiHost: 'https://your-api-host.com',
theme: 'light',
placeholder: 'Search here...',
width: '300px'
});
</script>
</body>
</html>
```
### Props
| **Prop** | **Type** | **Default Value** | **Description** |
|-----------------|-----------|-------------------------------------|--------------------------------------------------------------------------------------------------|
| **`apiKey`** | `string` | `"74039c6d-bff7-44ce-ae55-2973cbf13837"` | Your API key generated from the app. Used for authenticating requests. |
| **`apiHost`** | `string` | `"https://gptcloud.arc53.com"` | The base URL of the server hosting the vector similarity search and chatbot services. |
| **`theme`** | `"dark" \| "light"` | `"dark"` | The theme of the search bar. Accepts `"dark"` or `"light"`. |
| **`placeholder`** | `string` | `"Search or Ask AI..."` | Placeholder text displayed in the search input field. |
| **`width`** | `string` | `"256px"` | Width of the search bar. Accepts any valid CSS width value (e.g., `"300px"`, `"100%"`, `"20rem"`). |
Feel free to reach out if you need help customizing or extending the `SearchBar`!
## Our github
[DocsGPT](https://github.com/arc53/DocsGPT)
You can find the source code in the extensions/react-widget folder.
For more information about React, refer to this [link here](https://react.dev/learn)

View File

@@ -1,4 +1,4 @@
import { DocsGPTWidget } from "docsgpt";
import { DocsGPTWidget } from "docsgpt-react";
export default function MyApp({ Component, pageProps }) {
return (

View File

@@ -13,7 +13,7 @@ npm install docsgpt
### React
```javascript
import { DocsGPTWidget } from "docsgpt";
import { DocsGPTWidget } from "docsgpt-react";
const App = () => {
return <DocsGPTWidget />;
@@ -23,11 +23,11 @@ npm install docsgpt
To link the widget to your api and your documents you can pass parameters to the <DocsGPTWidget /> component.
```javascript
import { DocsGPTWidget } from "docsgpt";
import { DocsGPTWidget } from "docsgpt-react";
const App = () => {
return <DocsGPTWidget
apiHost="https://your-docsgpt-api.com"
apiHost="https://gptcloud.arc53.com"
apiKey=""
avatar = "https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png"
title = "Get AI assistance"
@@ -101,6 +101,75 @@ To link the widget to your api and your documents you can pass parameters to the
</html>
```
# SearchBar
The `SearchBar` component is an interactive search bar designed to provide search results based on **vector similarity search**. It also includes the capability to open the AI Chatbot, enabling users to query.
---
### Importing the Component
```tsx
import { SearchBar } from "docsgpt-react";
```
---
### Usage Example
```tsx
<SearchBar
apiKey="your-api-key"
apiHost="https://gptcloud.arc53.com"
theme="light"
placeholder="Search or Ask AI..."
width="300px"
/>
```
---
## HTML embedding for Search bar
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>SearchBar Embedding</title>
<script src="https://unpkg.com/docsgpt/dist/modern/main.js"></script> <!-- The bundled JavaScript file -->
</head>
<body>
<!-- Element where the SearchBar will render -->
<div id="search-bar-container"></div>
<script>
// Render the SearchBar into the specified element
renderSearchBar('search-bar-container', {
apiKey: 'your-api-key-here',
apiHost: 'https://your-api-host.com',
theme: 'light',
placeholder: 'Search here...',
width: '300px'
});
</script>
</body>
</html>
```
### Props
| **Prop** | **Type** | **Default Value** | **Description** |
|-----------------|-----------|-------------------------------------|--------------------------------------------------------------------------------------------------|
| **`apiKey`** | `string` | `"74039c6d-bff7-44ce-ae55-2973cbf13837"` | Your API key generated from the app. Used for authenticating requests. |
| **`apiHost`** | `string` | `"https://gptcloud.arc53.com"` | The base URL of the server hosting the vector similarity search and chatbot services. |
| **`theme`** | `"dark" \| "light"` | `"dark"` | The theme of the search bar. Accepts `"dark"` or `"light"`. |
| **`placeholder`** | `string` | `"Search or Ask AI..."` | Placeholder text displayed in the search input field. |
| **`width`** | `string` | `"256px"` | Width of the search bar. Accepts any valid CSS width value (e.g., `"300px"`, `"100%"`, `"20rem"`). |
Feel free to reach out if you need help customizing or extending the `SearchBar`!
## Our github
[DocsGPT](https://github.com/arc53/DocsGPT)

View File

@@ -1,12 +1,12 @@
{
"name": "docsgpt",
"version": "0.4.7",
"version": "0.4.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "docsgpt",
"version": "0.4.7",
"version": "0.4.8",
"license": "Apache-2.0",
"dependencies": {
"@babel/plugin-transform-flow-strip-types": "^7.23.3",
@@ -1885,6 +1885,17 @@
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
"integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
"dev": true,
"peer": true,
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
@@ -2258,7 +2269,6 @@
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/@parcel/core/-/core-2.12.0.tgz",
"integrity": "sha512-s+6pwEj+GfKf7vqGUzN9iSEPueUssCCQrCBUlcAfKrJe0a22hTUCjewpB0I7lNrCIULt8dkndD+sMdOrXsRl6Q==",
"dev": true,
"dependencies": {
"@mischnic/json-sourcemap": "^0.1.0",
"@parcel/cache": "2.12.0",
@@ -2298,7 +2308,6 @@
"version": "7.6.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
"dev": true,
"bin": {
"semver": "bin/semver.js"
},
@@ -2360,7 +2369,6 @@
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@parcel/graph/-/graph-3.2.0.tgz",
"integrity": "sha512-xlrmCPqy58D4Fg5umV7bpwDx5Vyt7MlnQPxW68vae5+BA4GSWetfZt+Cs5dtotMG2oCHzZxhIPt7YZ7NRyQzLA==",
"dev": true,
"dependencies": {
"nullthrows": "^1.1.1"
},
@@ -4560,7 +4568,7 @@
"version": "0.5.11",
"resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.11.tgz",
"integrity": "sha512-YNlnKRWF2sVojTpIyzwou9XoTNbzbzONwRhOoniEioF1AtaitTvVZblaQRrAzChWQ1bLYyYSWzM18y4WwgzJ+A==",
"dev": true,
"devOptional": true,
"dependencies": {
"tslib": "^2.4.0"
}
@@ -4590,6 +4598,35 @@
"@types/trusted-types": "*"
}
},
"node_modules/@types/eslint": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-9.6.1.tgz",
"integrity": "sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==",
"dev": true,
"peer": true,
"dependencies": {
"@types/estree": "*",
"@types/json-schema": "*"
}
},
"node_modules/@types/eslint-scope": {
"version": "3.7.7",
"resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz",
"integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==",
"dev": true,
"peer": true,
"dependencies": {
"@types/eslint": "*",
"@types/estree": "*"
}
},
"node_modules/@types/estree": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==",
"dev": true,
"peer": true
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -4621,6 +4658,16 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.10.1",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz",
"integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==",
"dev": true,
"peer": true,
"dependencies": {
"undici-types": "~6.20.0"
}
},
"node_modules/@types/parse-json": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
@@ -4662,11 +4709,198 @@
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"dev": true
},
"node_modules/@webassemblyjs/ast": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz",
"integrity": "sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==",
"dev": true,
"peer": true,
"dependencies": {
"@webassemblyjs/helper-numbers": "1.13.2",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2"
}
},
"node_modules/@webassemblyjs/floating-point-hex-parser": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.13.2.tgz",
"integrity": "sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==",
"dev": true,
"peer": true
},
"node_modules/@webassemblyjs/helper-api-error": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.13.2.tgz",
"integrity": "sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==",
"dev": true,
"peer": true
},
"node_modules/@webassemblyjs/helper-buffer": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.14.1.tgz",
"integrity": "sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==",
"dev": true,
"peer": true
},
"node_modules/@webassemblyjs/helper-numbers": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.13.2.tgz",
"integrity": "sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==",
"dev": true,
"peer": true,
"dependencies": {
"@webassemblyjs/floating-point-hex-parser": "1.13.2",
"@webassemblyjs/helper-api-error": "1.13.2",
"@xtuc/long": "4.2.2"
}
},
"node_modules/@webassemblyjs/helper-wasm-bytecode": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.13.2.tgz",
"integrity": "sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==",
"dev": true,
"peer": true
},
"node_modules/@webassemblyjs/helper-wasm-section": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.14.1.tgz",
"integrity": "sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==",
"dev": true,
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/wasm-gen": "1.14.1"
}
},
"node_modules/@webassemblyjs/ieee754": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.13.2.tgz",
"integrity": "sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==",
"dev": true,
"peer": true,
"dependencies": {
"@xtuc/ieee754": "^1.2.0"
}
},
"node_modules/@webassemblyjs/leb128": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.13.2.tgz",
"integrity": "sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==",
"dev": true,
"peer": true,
"dependencies": {
"@xtuc/long": "4.2.2"
}
},
"node_modules/@webassemblyjs/utf8": {
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.13.2.tgz",
"integrity": "sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==",
"dev": true,
"peer": true
},
"node_modules/@webassemblyjs/wasm-edit": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.14.1.tgz",
"integrity": "sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==",
"dev": true,
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/helper-wasm-section": "1.14.1",
"@webassemblyjs/wasm-gen": "1.14.1",
"@webassemblyjs/wasm-opt": "1.14.1",
"@webassemblyjs/wasm-parser": "1.14.1",
"@webassemblyjs/wast-printer": "1.14.1"
}
},
"node_modules/@webassemblyjs/wasm-gen": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.14.1.tgz",
"integrity": "sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==",
"dev": true,
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/ieee754": "1.13.2",
"@webassemblyjs/leb128": "1.13.2",
"@webassemblyjs/utf8": "1.13.2"
}
},
"node_modules/@webassemblyjs/wasm-opt": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.14.1.tgz",
"integrity": "sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==",
"dev": true,
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-buffer": "1.14.1",
"@webassemblyjs/wasm-gen": "1.14.1",
"@webassemblyjs/wasm-parser": "1.14.1"
}
},
"node_modules/@webassemblyjs/wasm-parser": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.14.1.tgz",
"integrity": "sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==",
"dev": true,
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@webassemblyjs/helper-api-error": "1.13.2",
"@webassemblyjs/helper-wasm-bytecode": "1.13.2",
"@webassemblyjs/ieee754": "1.13.2",
"@webassemblyjs/leb128": "1.13.2",
"@webassemblyjs/utf8": "1.13.2"
}
},
"node_modules/@webassemblyjs/wast-printer": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.14.1.tgz",
"integrity": "sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==",
"dev": true,
"peer": true,
"dependencies": {
"@webassemblyjs/ast": "1.14.1",
"@xtuc/long": "4.2.2"
}
},
"node_modules/@xtuc/ieee754": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz",
"integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==",
"dev": true,
"peer": true
},
"node_modules/@xtuc/long": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz",
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
"dev": true,
"peer": true
},
"node_modules/abortcontroller-polyfill": {
"version": "1.7.5",
"resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.5.tgz",
"integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ==",
"dev": true
"integrity": "sha512-JMJ5soJWP18htbbxJjG7bG6yuI6pRhgJ0scHHTfkUjf6wjP912xZWvM+A4sJK3gqd9E8fcPbDnOefbA9Th/FIQ=="
},
"node_modules/acorn": {
"version": "8.14.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz",
"integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==",
"dev": true,
"peer": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/ajv": {
"version": "6.12.6",
@@ -4771,7 +5005,6 @@
"version": "3.0.9",
"resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.9.tgz",
"integrity": "sha512-H7JU6iBHTal1gp56aKoaa//YUxEaAOUiydvrV/pILqIHXTtqxSkATOnDA2u+jZ/61sD+L/412+7kzXRtWukhpQ==",
"dev": true,
"dependencies": {
"safe-buffer": "^5.0.1"
}
@@ -4802,9 +5035,9 @@
}
},
"node_modules/browserslist": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz",
"integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==",
"version": "4.24.2",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz",
"integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==",
"funding": [
{
"type": "opencollective",
@@ -4820,10 +5053,10 @@
}
],
"dependencies": {
"caniuse-lite": "^1.0.30001587",
"electron-to-chromium": "^1.4.668",
"node-releases": "^2.0.14",
"update-browserslist-db": "^1.0.13"
"caniuse-lite": "^1.0.30001669",
"electron-to-chromium": "^1.5.41",
"node-releases": "^2.0.18",
"update-browserslist-db": "^1.1.1"
},
"bin": {
"browserslist": "cli.js"
@@ -4832,6 +5065,13 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"dev": true,
"peer": true
},
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
@@ -4860,9 +5100,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001625",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001625.tgz",
"integrity": "sha512-4KE9N2gcRH+HQhpeiRZXd+1niLB/XNLAhSy4z7fI8EzcbcPoAqjNInxVHTiTwWfTIV4w096XG8OtCOCQQKPv3w==",
"version": "1.0.30001680",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz",
"integrity": "sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==",
"funding": [
{
"type": "opencollective",
@@ -4876,7 +5116,8 @@
"type": "github",
"url": "https://github.com/sponsors/ai"
}
]
],
"license": "CC-BY-4.0"
},
"node_modules/chalk": {
"version": "2.4.2",
@@ -4922,7 +5163,6 @@
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==",
"dev": true,
"engines": {
"node": ">=0.8"
}
@@ -5132,7 +5372,6 @@
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-7.0.0.tgz",
"integrity": "sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==",
"dev": true,
"engines": {
"node": ">=6"
}
@@ -5140,13 +5379,12 @@
"node_modules/dotenv-expand": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz",
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==",
"dev": true
"integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA=="
},
"node_modules/electron-to-chromium": {
"version": "1.4.788",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.788.tgz",
"integrity": "sha512-ubp5+Ev/VV8KuRoWnfP2QF2Bg+O2ZFdb49DiiNbz2VmgkIqrnyYaqIOqj8A6K/3p1xV0QcU5hBQ1+BmB6ot1OA=="
"version": "1.5.72",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.72.tgz",
"integrity": "sha512-ZpSAUOZ2Izby7qnZluSrAlGgGQzucmFbN0n64dYzocYxnxV5ufurpj3VgEe4cUp7ir9LmeLxNYo8bVnlM8bQHw=="
},
"node_modules/emojis-list": {
"version": "3.0.0",
@@ -5157,6 +5395,20 @@
"node": ">= 4"
}
},
"node_modules/enhanced-resolve": {
"version": "5.17.1",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz",
"integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==",
"dev": true,
"peer": true,
"dependencies": {
"graceful-fs": "^4.2.4",
"tapable": "^2.2.0"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
@@ -5185,10 +5437,17 @@
"is-arrayish": "^0.2.1"
}
},
"node_modules/es-module-lexer": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
"integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
"dev": true,
"peer": true
},
"node_modules/escalade": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
"integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"engines": {
"node": ">=6"
}
@@ -5201,6 +5460,53 @@
"node": ">=0.8.0"
}
},
"node_modules/eslint-scope": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
"integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
"dev": true,
"peer": true,
"dependencies": {
"esrecurse": "^4.3.0",
"estraverse": "^4.1.1"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
"peer": true,
"dependencies": {
"estraverse": "^5.2.0"
},
"engines": {
"node": ">=4.0"
}
},
"node_modules/esrecurse/node_modules/estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
"peer": true,
"engines": {
"node": ">=4.0"
}
},
"node_modules/estraverse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
"integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
"dev": true,
"peer": true,
"engines": {
"node": ">=4.0"
}
},
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
@@ -5210,6 +5516,16 @@
"node": ">=0.10.0"
}
},
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
"integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==",
"dev": true,
"peer": true,
"engines": {
"node": ">=0.8.x"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -5300,6 +5616,13 @@
"node": ">=6"
}
},
"node_modules/glob-to-regexp": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz",
"integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==",
"dev": true,
"peer": true
},
"node_modules/globals": {
"version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
@@ -5308,6 +5631,13 @@
"node": ">=4"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"peer": true
},
"node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
@@ -5513,6 +5843,47 @@
"node": ">=0.12.0"
}
},
"node_modules/jest-worker": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz",
"integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==",
"dev": true,
"peer": true,
"dependencies": {
"@types/node": "*",
"merge-stream": "^2.0.0",
"supports-color": "^8.0.0"
},
"engines": {
"node": ">= 10.13.0"
}
},
"node_modules/jest-worker/node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"peer": true,
"engines": {
"node": ">=8"
}
},
"node_modules/jest-worker/node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"dev": true,
"peer": true,
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -5813,6 +6184,16 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
"integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA=="
},
"node_modules/loader-runner": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz",
"integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==",
"dev": true,
"peer": true,
"engines": {
"node": ">=6.11.5"
}
},
"node_modules/loader-utils": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz",
@@ -5902,6 +6283,13 @@
"integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==",
"license": "MIT"
},
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true,
"peer": true
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
@@ -5914,6 +6302,29 @@
"node": ">=8.6"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true,
"peer": true,
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"peer": true,
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -5960,9 +6371,9 @@
}
},
"node_modules/nanoid": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
"version": "3.3.8",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
"integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
"funding": [
{
"type": "github",
@@ -5976,6 +6387,13 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/neo-async": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==",
"dev": true,
"peer": true
},
"node_modules/node-addon-api": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.0.tgz",
@@ -6006,9 +6424,9 @@
}
},
"node_modules/node-releases": {
"version": "2.0.14",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz",
"integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw=="
"version": "2.0.19",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
"integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="
},
"node_modules/npm": {
"version": "10.8.1",
@@ -8615,9 +9033,9 @@
}
},
"node_modules/picocolors": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz",
"integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew=="
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
},
"node_modules/picomatch": {
"version": "2.3.1",
@@ -8750,6 +9168,16 @@
"node": ">=6"
}
},
"node_modules/randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
"integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
"dev": true,
"peer": true,
"dependencies": {
"safe-buffer": "^5.1.0"
}
},
"node_modules/react": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
@@ -8888,7 +9316,6 @@
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"dev": true,
"funding": [
{
"type": "github",
@@ -8938,6 +9365,16 @@
"semver": "bin/semver.js"
}
},
"node_modules/serialize-javascript": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
"integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==",
"dev": true,
"peer": true,
"dependencies": {
"randombytes": "^2.1.0"
}
},
"node_modules/shallowequal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
@@ -8959,6 +9396,17 @@
"node": ">=0.10.0"
}
},
"node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"dev": true,
"peer": true,
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
"node_modules/stable": {
"version": "0.1.8",
"resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz",
@@ -9025,6 +9473,16 @@
"resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
"integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ=="
},
"node_modules/tapable": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
"dev": true,
"peer": true,
"engines": {
"node": ">=6"
}
},
"node_modules/term-size": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/term-size/-/term-size-2.2.1.tgz",
@@ -9037,6 +9495,86 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/terser": {
"version": "5.37.0",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.37.0.tgz",
"integrity": "sha512-B8wRRkmre4ERucLM/uXx4MOV5cbnOlVAqUst+1+iLKPI0dOgFO28f84ptoQt9HEI537PMzfYa/d+GEPKTRXmYA==",
"dev": true,
"peer": true,
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.8.2",
"commander": "^2.20.0",
"source-map-support": "~0.5.20"
},
"bin": {
"terser": "bin/terser"
},
"engines": {
"node": ">=10"
}
},
"node_modules/terser-webpack-plugin": {
"version": "5.3.10",
"resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz",
"integrity": "sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w==",
"dev": true,
"peer": true,
"dependencies": {
"@jridgewell/trace-mapping": "^0.3.20",
"jest-worker": "^27.4.5",
"schema-utils": "^3.1.1",
"serialize-javascript": "^6.0.1",
"terser": "^5.26.0"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependencies": {
"webpack": "^5.1.0"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"esbuild": {
"optional": true
},
"uglify-js": {
"optional": true
}
}
},
"node_modules/terser-webpack-plugin/node_modules/schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"dev": true,
"peer": true,
"dependencies": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/terser/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"dev": true,
"peer": true
},
"node_modules/timsort": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz",
@@ -9083,7 +9621,6 @@
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -9098,6 +9635,13 @@
"integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==",
"license": "MIT"
},
"node_modules/undici-types": {
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
"integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==",
"dev": true,
"peer": true
},
"node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz",
@@ -9139,9 +9683,9 @@
}
},
"node_modules/update-browserslist-db": {
"version": "1.0.16",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz",
"integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==",
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz",
"integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==",
"funding": [
{
"type": "opencollective",
@@ -9157,8 +9701,8 @@
}
],
"dependencies": {
"escalade": "^3.1.2",
"picocolors": "^1.0.1"
"escalade": "^3.2.0",
"picocolors": "^1.1.0"
},
"bin": {
"update-browserslist-db": "cli.js"
@@ -9184,11 +9728,101 @@
"node": ">= 4"
}
},
"node_modules/watchpack": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz",
"integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
"dev": true,
"peer": true,
"dependencies": {
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.1.2"
},
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/weak-lru-cache": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/weak-lru-cache/-/weak-lru-cache-1.2.2.tgz",
"integrity": "sha512-DEAoo25RfSYMuTGc9vPJzZcZullwIqRDSI9LOy+fkCJPi6hykCnfKaXTuPBDuXAUcqHXyOgFtHNp/kB2FjYHbw=="
},
"node_modules/webpack": {
"version": "5.97.1",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz",
"integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==",
"dev": true,
"peer": true,
"dependencies": {
"@types/eslint-scope": "^3.7.7",
"@types/estree": "^1.0.6",
"@webassemblyjs/ast": "^1.14.1",
"@webassemblyjs/wasm-edit": "^1.14.1",
"@webassemblyjs/wasm-parser": "^1.14.1",
"acorn": "^8.14.0",
"browserslist": "^4.24.0",
"chrome-trace-event": "^1.0.2",
"enhanced-resolve": "^5.17.1",
"es-module-lexer": "^1.2.1",
"eslint-scope": "5.1.1",
"events": "^3.2.0",
"glob-to-regexp": "^0.4.1",
"graceful-fs": "^4.2.11",
"json-parse-even-better-errors": "^2.3.1",
"loader-runner": "^4.2.0",
"mime-types": "^2.1.27",
"neo-async": "^2.6.2",
"schema-utils": "^3.2.0",
"tapable": "^2.1.1",
"terser-webpack-plugin": "^5.3.10",
"watchpack": "^2.4.1",
"webpack-sources": "^3.2.3"
},
"bin": {
"webpack": "bin/webpack.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
},
"peerDependenciesMeta": {
"webpack-cli": {
"optional": true
}
}
},
"node_modules/webpack-sources": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz",
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
"dev": true,
"peer": true,
"engines": {
"node": ">=10.13.0"
}
},
"node_modules/webpack/node_modules/schema-utils": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.3.0.tgz",
"integrity": "sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg==",
"dev": true,
"peer": true,
"dependencies": {
"@types/json-schema": "^7.0.8",
"ajv": "^6.12.5",
"ajv-keywords": "^3.5.2"
},
"engines": {
"node": ">= 10.13.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/webpack"
}
},
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "docsgpt",
"version": "0.4.7",
"name": "docsgpt-react",
"version": "0.4.8",
"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",
@@ -30,9 +30,10 @@
"styled-components": "^5"
},
"scripts": {
"build": "parcel build src/main.tsx --public-url ./",
"build": "parcel build src/browser.tsx --public-url ./",
"build:react": "parcel build src/index.ts",
"dev": "parcel src/index.html -p 3000",
"serve": "parcel serve -p 3000",
"dev": "parcel -p 3000",
"test": "jest",
"lint": "eslint",
"check": "tsc --noEmit",

View File

@@ -1,11 +1,11 @@
import React from "react"
import {DocsGPTWidget} from "./components/DocsGPTWidget"
const App = () => {
import {SearchBar} from "./components/SearchBar"
export const App = () => {
return (
<div>
<SearchBar/>
<DocsGPTWidget/>
</div>
)
}
export default App
}

View File

@@ -0,0 +1,22 @@
//exports browser ready methods
import { createRoot } from "react-dom/client";
import { DocsGPTWidget } from './components/DocsGPTWidget';
import { SearchBar } from './components/SearchBar';
import React from "react";
if (typeof window !== 'undefined') {
const renderWidget = (elementId: string, props = {}) => {
const root = createRoot(document.getElementById(elementId) as HTMLElement);
root.render(<DocsGPTWidget {...props} />);
};
const renderSearchBar = (elementId: string, props = {}) => {
const root = createRoot(document.getElementById(elementId) as HTMLElement);
root.render(<SearchBar {...props} />);
};
(window as any).renderDocsGPTWidget = renderWidget;
(window as any).renderSearchBar = renderSearchBar;
}
export { DocsGPTWidget, SearchBar };

View File

@@ -1,9 +1,9 @@
"use client";
import React, { useRef } from 'react'
import DOMPurify from 'dompurify';
import styled, { keyframes, createGlobalStyle } from 'styled-components';
import styled, { keyframes, css } from 'styled-components';
import { PaperPlaneIcon, RocketIcon, ExclamationTriangleIcon, Cross2Icon } from '@radix-ui/react-icons';
import { FEEDBACK, MESSAGE_TYPE, Query, Status, WidgetProps } from '../types/index';
import { FEEDBACK, MESSAGE_TYPE, Query, Status, WidgetCoreProps, WidgetProps } from '../types/index';
import { fetchAnswerStreaming, sendFeedback } from '../requests/streamingApi';
import { ThemeProvider } from 'styled-components';
import Like from "../assets/like.svg"
@@ -49,7 +49,86 @@ const sizesConfig = {
maxHeight: custom.maxHeight || '70vh',
}),
};
const createBox = keyframes`
0% {
transform: scale(0.6);
}
90% {
transform: scale(1.02);
}
100% {
transform: scale(1);
}
`
const closeBox = keyframes`
0% {
transform: scale(1);
}
10% {
transform: scale(1.02);
}
100% {
transform: scale(0.6);
}
`
const openContainer = keyframes`
0% {
width: 200px;
height: 100px;
}
100% {
width: ${(props) => props.theme.dimensions.width};
height: ${(props) => props.theme.dimensions.height};
border-radius: 12px;
}`
const closeContainer = keyframes`
0% {
width: ${(props) => props.theme.dimensions.width};
height: ${(props) => props.theme.dimensions.height};
border-radius: 12px;
}
100% {
width: 200px;
height: 100px;
}
`
const fadeIn = keyframes`
from {
opacity: 0;
width: ${(props) => props.theme.dimensions.width};
height: ${(props) => props.theme.dimensions.height};
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
width: ${(props) => props.theme.dimensions.width};
height: ${(props) => props.theme.dimensions.height};
}
`
const fadeOut = keyframes`
from {
opacity: 1;
width: ${(props) => props.theme.dimensions.width};
height: ${(props) => props.theme.dimensions.height};
}
to {
opacity: 0;
transform: scale(0.9);
width: ${(props) => props.theme.dimensions.width};
height: ${(props) => props.theme.dimensions.height};
}
`
const scaleAnimation = keyframes`
from {
transform: scale(1.2);
}
to {
transform: scale(1);
}
`
const Overlay = styled.div`
position: fixed;
top: 0;
@@ -60,53 +139,35 @@ const Overlay = styled.div`
z-index: 999;
transition: opacity 0.5s;
`
const WidgetContainer = styled.div<{ modal?: boolean, isOpen?: boolean }>`
all: initial;
position: fixed;
right: ${props => props.modal ? '50%' : '10px'};
bottom: ${props => props.modal ? '50%' : '10px'};
z-index: 1000;
display: none;
z-index: 1001;
transform-origin:100% 100%;
display: block;
&.modal{
transform : translate(50%,50%);
}
&.open {
animation: createBox 250ms cubic-bezier(0.25, 0.1, 0.25, 1) forwards;
animation: css ${createBox} 250ms cubic-bezier(0.25, 0.1, 0.25, 1) forwards;
}
&.close {
animation: closeBox 250ms cubic-bezier(0.25, 0.1, 0.25, 1) forwards;
animation: css ${closeBox} 250ms cubic-bezier(0.25, 0.1, 0.25, 1) forwards;
}
${props => props.modal &&
"transform : translate(50%,50%);"
}
align-items: center;
text-align: left;
@keyframes createBox {
0% {
transform: scale(0.6);
}
90% {
transform: scale(1.02);
}
100% {
transform: scale(1);
}
}
@keyframes closeBox {
0% {
transform: scale(1);
}
10% {
transform: scale(1.02);
}
100% {
transform: scale(0.6);
}
}
`;
const StyledContainer = styled.div<{ isOpen: boolean }>`
all: initial;
max-height: ${(props) => props.theme.dimensions.maxHeight};
max-width: ${(props) => props.theme.dimensions.maxWidth};
width: ${(props) => props.theme.dimensions.width};
height: ${(props) => props.theme.dimensions.height} ;
position: relative;
flex-direction: column;
justify-content: space-between;
@@ -119,68 +180,20 @@ const StyledContainer = styled.div<{ isOpen: boolean }>`
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05), 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 26px 26px 0px 26px;
animation: ${({ isOpen, theme }) =>
theme.dimensions.size === 'large'
? isOpen
? 'fadeIn 150ms ease-in forwards'
: 'fadeOut 150ms ease-in forwards'
: isOpen
? 'openContainer 150ms ease-in forwards'
: 'closeContainer 250ms ease-in forwards'};
@keyframes openContainer {
0% {
width: 200px;
height: 100px;
}
100% {
width: ${(props) => props.theme.dimensions.width};
height: ${(props) => props.theme.dimensions.height};
border-radius: 12px;
}
}
@keyframes closeContainer {
0% {
width: ${(props) => props.theme.dimensions.width};
height: ${(props) => props.theme.dimensions.height};
border-radius: 12px;
}
100% {
width: 200px;
height: 100px;
}
}
@keyframes fadeIn {
from {
opacity: 0;
width: ${(props) => props.theme.dimensions.width};
height: ${(props) => props.theme.dimensions.height};
transform: scale(0.9);
}
to {
opacity: 1;
transform: scale(1);
width: ${(props) => props.theme.dimensions.width};
height: ${(props) => props.theme.dimensions.height};
}
}
@keyframes fadeOut {
from {
opacity: 1;
width: ${(props) => props.theme.dimensions.width};
height: ${(props) => props.theme.dimensions.height};
}
to {
opacity: 0;
transform: scale(0.9);
width: ${(props) => props.theme.dimensions.width};
height: ${(props) => props.theme.dimensions.height};
}
}
theme.dimensions.size === 'large'
? isOpen
? css`${fadeIn} 150ms ease-in forwards`
: css` ${fadeOut} 150ms ease-in forwards`
: isOpen
? css`${openContainer} 150ms ease-in forwards`
: css`${closeContainer} 250ms ease-in forwards`};
@media only screen and (max-width: 768px) {
max-height: 100vh;
max-width: 80vw;
overflow: auto;
}
`;
const FloatingButton = styled.div<{ bgcolor: string, hidden: boolean, isAnimatingButton: boolean }>`
position: fixed;
display: ${props => props.hidden ? "none" : "flex"};
@@ -198,7 +211,7 @@ const FloatingButton = styled.div<{ bgcolor: string, hidden: boolean, isAnimatin
background: ${props => props.bgcolor};
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
cursor: pointer;
animation: ${props => props.isAnimatingButton ? 'scaleAnimation 200ms forwards' : 'none'};
animation: ${props => props.isAnimatingButton ? css`${scaleAnimation} 200ms forwards` : 'none'};
&:hover {
transform: scale(1.1);
transition: transform 0.2s ease-in-out;
@@ -206,15 +219,6 @@ const FloatingButton = styled.div<{ bgcolor: string, hidden: boolean, isAnimatin
&:not(:hover) {
transition: transform 0.2s ease-in-out;
}
@keyframes scaleAnimation {
from {
transform: scale(1.2);
}
to {
transform: scale(1);
}
}
`;
const CancelButton = styled.button`
cursor: pointer;
@@ -478,7 +482,47 @@ const Hero = ({ title, description, theme }: { title: string, description: strin
</HeroContainer>
);
};
export const DocsGPTWidget = ({
export const DocsGPTWidget = (props: WidgetProps) => {
const {
buttonIcon = 'https://d3dg1063dc54p9.cloudfront.net/widget/chat.svg',
buttonText = 'Ask a question',
buttonBg = 'linear-gradient(to bottom right, #5AF0EC, #E80D9D)',
defaultOpen = false,
...coreProps
} = props
const [open, setOpen] = React.useState<boolean>(defaultOpen);
const [isAnimatingButton, setIsAnimatingButton] = React.useState(false);
const [isFloatingButtonVisible, setIsFloatingButtonVisible] = React.useState(true);
React.useEffect(() => {
if (isFloatingButtonVisible)
setTimeout(() => setIsAnimatingButton(true), 250);
return () => {
setIsAnimatingButton(false)
}
}, [isFloatingButtonVisible])
const handleClose = () => {
setIsFloatingButtonVisible(true);
setOpen(false);
};
const handleOpen = () => {
setOpen(true);
setIsFloatingButtonVisible(false);
}
return (
<>
<FloatingButton bgcolor={buttonBg} onClick={handleOpen} hidden={!isFloatingButtonVisible} isAnimatingButton={isAnimatingButton}>
<img width={24} src={buttonIcon} />
<span>{buttonText}</span>
</FloatingButton>
<WidgetCore isOpen={open} handleClose={handleClose} {...coreProps} />
</>
)
}
export const WidgetCore = ({
apiHost = 'https://gptcloud.arc53.com',
apiKey = '82962c9a-aa77-4152-94e5-a4f84fd44c6a',
avatar = 'https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png',
@@ -488,25 +532,37 @@ export const DocsGPTWidget = ({
heroDescription = 'This chatbot is built with DocsGPT and utilises GenAI, please review important information using sources.',
size = 'small',
theme = 'dark',
buttonIcon = 'https://d3dg1063dc54p9.cloudfront.net/widget/chat.svg',
buttonText = 'Ask a question',
buttonBg = 'linear-gradient(to bottom right, #5AF0EC, #E80D9D)',
collectFeedback = true,
deafultOpen = false
}: WidgetProps) => {
const [prompt, setPrompt] = React.useState('');
isOpen = false,
prefilledQuery = "",
handleClose
}: WidgetCoreProps) => {
const [prompt, setPrompt] = React.useState<string>("");
const [mounted, setMounted] = React.useState(false);
const [status, setStatus] = React.useState<Status>('idle');
const [queries, setQueries] = React.useState<Query[]>([])
const [conversationId, setConversationId] = React.useState<string | null>(null)
const [open, setOpen] = React.useState<boolean>(deafultOpen)
const [queries, setQueries] = React.useState<Query[]>([]);
const [conversationId, setConversationId] = React.useState<string | null>(null);
const [eventInterrupt, setEventInterrupt] = React.useState<boolean>(false); //click or scroll by user while autoScrolling
const [isAnimatingButton, setIsAnimatingButton] = React.useState(false);
const [isFloatingButtonVisible, setIsFloatingButtonVisible] = React.useState(true);
const isBubbleHovered = useRef<boolean>(false)
const widgetRef = useRef<HTMLDivElement>(null)
const isBubbleHovered = useRef<boolean>(false);
const endMessageRef = React.useRef<HTMLDivElement | null>(null);
const md = new MarkdownIt();
React.useEffect(() => {
if (isOpen) {
setMounted(true); // Mount the component
appendQuery(prefilledQuery)
} else {
// Wait for animations before unmounting
const timeout = setTimeout(() => {
setMounted(false)
}, 250);
return () => clearTimeout(timeout);
}
}, [isOpen]);
const handleUserInterrupt = () => {
(status === 'loading') && setEventInterrupt(true);
}
@@ -606,144 +662,138 @@ export const DocsGPTWidget = ({
}
// submit handler
const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
e.preventDefault();
await appendQuery(prompt)
}
const appendQuery = async (userQuery:string) => {
console.log(userQuery)
if(!userQuery)
return;
setEventInterrupt(false);
queries.push({ prompt })
setPrompt('')
await stream(prompt)
queries.push({ prompt:userQuery});
setPrompt('');
await stream(userQuery);
}
const handleImageError = (event: React.SyntheticEvent<HTMLImageElement, Event>) => {
event.currentTarget.src = "https://d3dg1063dc54p9.cloudfront.net/cute-docsgpt.png";
};
const handleClose = () => {
setOpen(false);
setTimeout(() => {
if (widgetRef.current) widgetRef.current.style.display = "none";
setIsFloatingButtonVisible(true);
setIsAnimatingButton(true);
setTimeout(() => setIsAnimatingButton(false), 200);
}, 250)
};
const handleOpen = () => {
setOpen(true);
setIsFloatingButtonVisible(false);
if (widgetRef.current)
widgetRef.current.style.display = 'block'
}
const dimensions =
typeof size === 'object' && 'custom' in size
? sizesConfig.getCustom(size.custom)
: sizesConfig[size];
if (!mounted) return null;
return (
<ThemeProvider theme={{ ...themes[theme], dimensions }}>
{open && size === 'large' &&
{isOpen && size === 'large' &&
<Overlay onClick={handleClose} />
}
<FloatingButton bgcolor={buttonBg} onClick={handleOpen} hidden={!isFloatingButtonVisible} isAnimatingButton={isAnimatingButton}>
<img width={24} src={buttonIcon} />
<span>{buttonText}</span>
</FloatingButton>
<WidgetContainer ref={widgetRef} className={`${size != "large" && (open ? "open" : "close")}`} modal={size == 'large'}>
{<StyledContainer isOpen={open}>
<div>
<CancelButton onClick={handleClose}>
<Cross2Icon width={24} height={24} color={theme === 'light' ? 'black' : 'white'} />
</CancelButton>
<Header>
<img style={{ transform: 'translateY(-5px)', maxWidth: "42px", maxHeight: "42px" }} onError={handleImageError} src={avatar} alt='docs-gpt' />
<ContentWrapper>
<Title>{title}</Title>
<Description>{description}</Description>
</ContentWrapper>
</Header>
</div>
<Conversation onWheel={handleUserInterrupt} onTouchMove={handleUserInterrupt}>
{
queries.length > 0 ? queries?.map((query, index) => {
return (
<React.Fragment key={index}>
{
query.prompt && <MessageBubble type='QUESTION'>
<Message
type='QUESTION'
ref={(!(query.response || query.error) && index === queries.length - 1) ? endMessageRef : null}>
{query.prompt}
</Message>
</MessageBubble>
}
{
query.response ? <MessageBubble onMouseOver={() => { isBubbleHovered.current = true }} type='ANSWER'>
<Message
type='ANSWER'
ref={(index === queries.length - 1) ? endMessageRef : null}
>
<Markdown
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(md.render(query.response)) }}
/>
</Message>
{(
<WidgetContainer className={`${size !== 'large' ? (isOpen ? "open" : "close") : "modal"}`} modal={size === 'large'}>
<StyledContainer isOpen={isOpen}>
<div>
<CancelButton onClick={handleClose}>
<Cross2Icon width={24} height={24} color={theme === 'light' ? 'black' : 'white'} />
</CancelButton>
<Header>
<img style={{ transform: 'translateY(-5px)', maxWidth: "42px", maxHeight: "42px" }} onError={handleImageError} src={avatar} alt='docs-gpt' />
<ContentWrapper>
<Title>{title}</Title>
<Description>{description}</Description>
</ContentWrapper>
</Header>
</div>
<Conversation onWheel={handleUserInterrupt} onTouchMove={handleUserInterrupt}>
{
queries.length > 0 ? queries?.map((query, index) => {
return (
<React.Fragment key={index}>
{
query.prompt && <MessageBubble type='QUESTION'>
<Message
type='QUESTION'
ref={(!(query.response || query.error) && index === queries.length - 1) ? endMessageRef : null}>
{query.prompt}
</Message>
</MessageBubble>
}
{
query.response ? <MessageBubble onMouseOver={() => { isBubbleHovered.current = true }} type='ANSWER'>
<Message
type='ANSWER'
ref={(index === queries.length - 1) ? endMessageRef : null}
>
<Markdown
dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(md.render(query.response)) }}
/>
</Message>
{collectFeedback &&
<Feedback>
<Like
style={{
stroke: query.feedback == 'LIKE' ? '#8860DB' : '#c0c0c0',
visibility: query.feedback == 'LIKE' ? 'visible' : 'hidden'
}}
fill='none'
onClick={() => handleFeedback("LIKE", index)} />
<Dislike
style={{
stroke: query.feedback == 'DISLIKE' ? '#ed8085' : '#c0c0c0',
visibility: query.feedback == 'DISLIKE' ? 'visible' : 'hidden'
}}
fill='none'
onClick={() => handleFeedback("DISLIKE", index)} />
</Feedback>}
</MessageBubble>
: <div>
{
query.error ? <ErrorAlert>
{collectFeedback &&
<Feedback>
<Like
style={{
stroke: query.feedback == 'LIKE' ? '#8860DB' : '#c0c0c0',
visibility: query.feedback == 'LIKE' ? 'visible' : 'hidden'
}}
fill='none'
onClick={() => handleFeedback("LIKE", index)} />
<Dislike
style={{
stroke: query.feedback == 'DISLIKE' ? '#ed8085' : '#c0c0c0',
visibility: query.feedback == 'DISLIKE' ? 'visible' : 'hidden'
}}
fill='none'
onClick={() => handleFeedback("DISLIKE", index)} />
</Feedback>}
</MessageBubble>
: <div>
{
query.error ? <ErrorAlert>
<ExclamationTriangleIcon width={22} height={22} color='#b91c1c' />
<div>
<h5 style={{ margin: 2 }}>Network Error</h5>
<span style={{ margin: 2, fontSize: '13px' }}>{query.error}</span>
</div>
</ErrorAlert>
: <MessageBubble type='ANSWER'>
<Message type='ANSWER' style={{ fontWeight: 600 }}>
<DotAnimation>.</DotAnimation>
<Delay delay={200}>.</Delay>
<Delay delay={400}>.</Delay>
</Message>
</MessageBubble>
}
</div>
}
</React.Fragment>)
})
: <Hero title={heroTitle} description={heroDescription} theme={theme} />
}
</Conversation>
<div>
<PromptContainer
onSubmit={handleSubmit}>
<StyledInput
value={prompt} onChange={(event) => setPrompt(event.target.value)}
type='text' placeholder="Ask your question" />
<StyledButton
disabled={prompt.trim().length == 0 || status !== 'idle'}>
<PaperPlaneIcon width={18} height={18} color='white' />
</StyledButton>
</PromptContainer>
<Tagline>
Powered by&nbsp;
<Hyperlink target='_blank' href='https://www.docsgpt.cloud/'>DocsGPT</Hyperlink>
</Tagline>
</div>
</StyledContainer>}
</WidgetContainer>
<ExclamationTriangleIcon width={22} height={22} color='#b91c1c' />
<div>
<h5 style={{ margin: 2 }}>Network Error</h5>
<span style={{ margin: 2, fontSize: '13px' }}>{query.error}</span>
</div>
</ErrorAlert>
: <MessageBubble type='ANSWER'>
<Message type='ANSWER' style={{ fontWeight: 600 }}>
<DotAnimation>.</DotAnimation>
<Delay delay={200}>.</Delay>
<Delay delay={400}>.</Delay>
</Message>
</MessageBubble>
}
</div>
}
</React.Fragment>)
})
: <Hero title={heroTitle} description={heroDescription} theme={theme} />
}
</Conversation>
<div>
<PromptContainer
onSubmit={handleSubmit}>
<StyledInput
autoFocus
value={prompt} onChange={(event) => setPrompt(event.target.value)}
type='text' placeholder="Ask your question" />
<StyledButton
disabled={prompt.trim().length == 0 || status !== 'idle'}>
<PaperPlaneIcon width={18} height={18} color='white' />
</StyledButton>
</PromptContainer>
<Tagline>
Powered by&nbsp;
<Hyperlink target='_blank' href='https://www.docsgpt.cloud/'>DocsGPT</Hyperlink>
</Tagline>
</div>
</StyledContainer>
</WidgetContainer>
)
}
</ThemeProvider>
)
}

View File

@@ -0,0 +1,405 @@
import React from 'react'
import styled, { ThemeProvider } from 'styled-components';
import { WidgetCore } from './DocsGPTWidget';
import { SearchBarProps } from '@/types';
import { getSearchResults } from '../requests/searchAPI'
import { Result } from '@/types';
import MarkdownIt from 'markdown-it';
import { getOS, preprocessSearchResultsToHTML } from '../utils/helper'
const themes = {
dark: {
bg: '#000',
text: '#fff',
primary: {
text: "#FAFAFA",
bg: '#111111'
},
secondary: {
text: "#A1A1AA",
bg: "#38383b"
}
},
light: {
bg: '#fff',
text: '#000',
primary: {
text: "#222327",
bg: "#fff"
},
secondary: {
text: "#A1A1AA",
bg: "#F6F6F6"
}
}
}
const Main = styled.div`
all:initial;
font-family: sans-serif;
`
const TextField = styled.input<{ inputWidth: string }>`
padding: 6px 6px;
width: ${({ inputWidth }) => inputWidth};
border-radius: 8px;
display: inline;
color: ${props => props.theme.primary.text};
outline: none;
border: none;
background-color: ${props => props.theme.secondary.bg};
-webkit-appearance: none;
-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};
}
`
const Container = styled.div`
position: relative;
display: inline-block;
`
const SearchResults = styled.div`
position: absolute;
display: block;
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%;
z-index: 100;
height: 25vh;
overflow-y: auto;
top: 32px;
color: ${props => props.theme.primary.text};
scrollbar-color: lab(48.438 0 0 / 0.4) rgba(0, 0, 0, 0);
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;
}
`
const Title = styled.h3`
font-size: 14px;
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};
`
const Content = styled.div`
font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
`
const ResultWrapper = styled.div`
padding: 4px 8px 4px 8px;
border-radius: 8px;
cursor: pointer;
&.contains-source:hover{
background-color: rgba(0, 92, 197, 0.15);
${Title} {
color: rgb(0, 126, 230);
}
}
`
const Markdown = styled.div`
line-height:20px;
font-size: 12px;
white-space: pre-wrap;
pre {
padding: 8px;
width: 90%;
font-size: 12px;
border-radius: 6px;
overflow-x: auto;
background-color: #1B1C1F;
color: #fff ;
}
h1,h2 {
font-size: 16px;
font-weight: 600;
color: ${(props) => props.theme.text};
opacity: 0.8;
}
h3 {
font-size: 14px;
}
p {
margin: 0px;
line-height: 1.35rem;
font-size: 12px;
}
code:not(pre code) {
border-radius: 6px;
padding: 2px 2px;
margin: 2px;
font-size: 10px;
display: inline;
background-color: #646464;
color: #fff ;
}
img{
max-width: 50%;
}
code {
overflow-x: auto;
}
a{
color: #007ee6;
}
`
const Toolkit = styled.kbd`
position: absolute;
right: 4px;
top: 4px;
background-color: ${(props) => props.theme.primary.bg};
color: ${(props) => props.theme.secondary.text};
font-weight: 600;
font-size: 10px;
padding: 3px;
border: 1px solid ${(props) => props.theme.secondary.text};
border-radius: 4px;
`
const Loader = styled.div`
margin: 2rem auto;
border: 4px solid ${props => props.theme.secondary.text};
border-top: 4px solid ${props => props.theme.primary.bg};
border-radius: 50%;
width: 12px;
height: 12px;
animation: spin 1s linear infinite;
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
`;
const NoResults = styled.div`
margin-top: 2rem;
text-align: center;
font-size: 1rem;
color: #888;
`;
const InfoButton = styled.button`
cursor: pointer;
padding: 10px 4px 10px 4px;
display: block;
width: 100%;
color: inherit;
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};
`
export const SearchBar = ({
apiKey = "74039c6d-bff7-44ce-ae55-2973cbf13837",
apiHost = "https://gptcloud.arc53.com",
theme = "dark",
placeholder = "Search or Ask AI...",
width = "256px"
}: 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 [results, setResults] = React.useState<Result[]>([]);
const debounceTimeout = React.useRef<ReturnType<typeof setTimeout> | null>(null);
const abortControllerRef = React.useRef<AbortController | null>(null)
const browserOS = getOS();
function isTouchDevice() {
return 'ontouchstart' in window;
}
const isTouch = isTouchDevice();
const getKeyboardInstruction = () => {
if (isResultVisible) return "Enter"
if (browserOS === 'mac')
return "⌘ K"
else
return "Ctrl K"
}
React.useEffect(() => {
const handleFocusSearch = (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(false);
}
};
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('keydown', handleFocusSearch);
return () => {
setIsResultVisible(true);
document.removeEventListener('mousedown', handleClickOutside);
};
}, [])
React.useEffect(() => {
if (!input) {
setResults([]);
return;
}
setLoading(true);
if (debounceTimeout.current) {
clearTimeout(debounceTimeout.current);
}
if (abortControllerRef.current) {
abortControllerRef.current.abort();
}
const abortController = new AbortController();
abortControllerRef.current = abortController;
debounceTimeout.current = setTimeout(() => {
getSearchResults(input, apiKey, apiHost, abortController.signal)
.then((data) => setResults(data))
.catch((err) => !abortController.signal.aborted && console.log(err))
.finally(() => setLoading(false));
}, 500);
return () => {
console.log(results);
abortController.abort();
clearTimeout(debounceTimeout.current ?? undefined);
};
}, [input])
const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') {
event.preventDefault();
openWidget();
}
};
const openWidget = () => {
setIsWidgetOpen(true);
setIsResultVisible(false)
}
const handleClose = () => {
setIsWidgetOpen(false);
}
const md = new MarkdownIt();
return (
<ThemeProvider theme={{ ...themes[theme] }}>
<Main>
<Container ref={containerRef}>
<TextField
spellCheck={false}
inputWidth={width}
onFocus={() => setIsResultVisible(true)}
ref={inputRef}
onSubmit={() => setIsWidgetOpen(true)}
onKeyDown={(e) => handleKeyDown(e)}
placeholder={placeholder}
value={input}
onChange={(e) => setInput(e.target.value)}
/>
{
input.length > 0 && isResultVisible && (
<SearchResults>
<InfoButton onClick={openWidget}>
{
isTouch ?
"Ask the AI" :
<>
Press <span style={{ fontSize: "16px" }}>&crarr;</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 />
}
</SearchResults>
)
}
{
isTouch ?
<Toolkit
onClick={() => {
setIsWidgetOpen(true)
}}
title={"Tap to Ask the AI"}>
Tap
</Toolkit>
:
<Toolkit
title={getKeyboardInstruction() === "Enter" ? "Press Enter to Ask AI" : ""}>
{getKeyboardInstruction()}
</Toolkit>
}
</Container>
<WidgetCore
theme={theme}
apiHost={apiHost}
apiKey={apiKey}
prefilledQuery={input}
isOpen={isWidgetOpen}
handleClose={handleClose} size={"large"}
/>
</Main>
</ThemeProvider>
)
}

View File

@@ -9,11 +9,11 @@
<body>
<div id="app"></div>
<script type="module" src="main.tsx"></script>
<script type="module" src="../dist/main.js"></script>
<script type="module">
<!-- <script type="module">
window.onload = function() {
renderDocsGPTWidget('app');
renderSearchBar('app')
}
</script>
</script> -->
</body>
</html>

View File

@@ -1 +1,3 @@
export { DocsGPTWidget } from "./components/DocsGPTWidget";
//exports methods for React
export {SearchBar} from "./components/SearchBar"
export { DocsGPTWidget } from "./components/DocsGPTWidget";

View File

@@ -1,12 +1,7 @@
import React from 'react';
import { createRoot } from 'react-dom/client';
import { DocsGPTWidget } from './components/DocsGPTWidget';
if (typeof window !== 'undefined') {
const renderWidget = (elementId: string, props = {}) => {
const root = createRoot(document.getElementById(elementId) as HTMLElement);
root.render(<DocsGPTWidget {...props} />);
};
(window as any).renderDocsGPTWidget = renderWidget;
}
export { DocsGPTWidget };
//development
import { createRoot } from "react-dom/client";
import { App } from "./App";
import React from "react";
const container = document.getElementById("app") as HTMLElement;
const root = createRoot(container)
root.render(<App />);

View File

@@ -0,0 +1,37 @@
import { Result } from "@/types";
async function getSearchResults(question: string, apiKey: string, apiHost: string, signal: AbortSignal): Promise<Result[]> {
const payload = {
question,
api_key: apiKey
};
try {
const response = await fetch(`${apiHost}/api/search`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
signal: signal
});
if (!response.ok) {
throw new Error(`Error: ${response.status}`);
}
const data: Result[] = await response.json();
return data;
} catch (error) {
if (!(error instanceof DOMException && error.name == "AbortError")) {
console.error("Failed to fetch documents:", error);
}
throw error;
}
}
export {
getSearchResults
}

View File

@@ -32,5 +32,25 @@ export interface WidgetProps {
buttonText?:string;
buttonBg?:string;
collectFeedback?:boolean;
deafultOpen?: boolean;
}
defaultOpen?: boolean;
}
export interface WidgetCoreProps extends WidgetProps {
widgetRef?:React.RefObject<HTMLDivElement> | null;
handleClose?:React.MouseEventHandler | undefined;
isOpen:boolean;
prefilledQuery?: string;
}
export interface SearchBarProps {
apiHost?: string;
apiKey?: string;
theme?:THEME;
placeholder?:string;
width?:string;
}
export interface Result {
text:string;
title:string;
source:string;
}

View File

@@ -0,0 +1,87 @@
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;
if (/Mac/i.test(platform)) {
return 'mac';
}
if (/Win/i.test(platform)) {
return 'win';
}
if (/Linux/i.test(platform) && !/Android/i.test(userAgent)) {
return 'linux';
}
if (/Android/i.test(userAgent)) {
return 'android';
}
if (/iPhone|iPad|iPod/i.test(userAgent)) {
return 'ios';
}
return 'other';
};
export const preprocessSearchResultsToHTML = (text: string, keyword: string) => {
const md = new MarkdownIt();
const htmlString = md.render(text);
// Container for processed HTML
const filteredResults = document.createElement("div");
filteredResults.innerHTML = htmlString;
if (!processNode(filteredResults, keyword.trim())) return null;
return filteredResults.innerHTML.trim() ? filteredResults.outerHTML : null;
};
// Recursive function to process nodes
const processNode = (node: Node, keyword: string): boolean => {
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);
}
node.parentNode?.removeChild(node);
return true;
}
return false;
} else if (node.nodeType === Node.ELEMENT_NODE) {
const children = Array.from(node.childNodes);
let hasKeyword = false;
children.forEach((child) => {
if (!processNode(child, keyword)) {
node.removeChild(child);
} else {
hasKeyword = true;
}
});
return hasKeyword;
}
return false;
};

View File

@@ -21,7 +21,7 @@
/* Linting */
"strict": true,
"noUnusedLocals": false,
"noUnusedParameters": true,
"noUnusedParameters": false,
"noFallthroughCasesInSwitch": true,
/* The "typeRoots" configuration specifies the locations where
TypeScript looks for type definitions (.d.ts files) to

View File

@@ -5,7 +5,7 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0,viewport-fit=cover" />
<meta name="apple-mobile-web-app-capable" content="yes">
<title>DocsGPT 🦖</title>
<title>DocsGPT</title>
<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico" />
</head>

File diff suppressed because it is too large Load Diff

View File

@@ -28,7 +28,8 @@
"react-chartjs-2": "^5.2.0",
"react-copy-to-clipboard": "^5.1.0",
"react-dom": "^18.3.1",
"react-dropzone": "^14.2.3",
"react-helmet": "^6.1.0",
"react-dropzone": "^14.3.5",
"react-i18next": "^15.0.2",
"react-markdown": "^9.0.1",
"react-redux": "^8.0.5",
@@ -41,28 +42,29 @@
"devDependencies": {
"@types/react": "^18.0.27",
"@types/react-dom": "^18.3.0",
"@types/react-helmet": "^6.1.11",
"@types/react-syntax-highlighter": "^15.5.13",
"@typescript-eslint/eslint-plugin": "^5.51.0",
"@typescript-eslint/parser": "^5.62.0",
"@vitejs/plugin-react": "^4.3.1",
"@vitejs/plugin-react": "^4.3.4",
"autoprefixer": "^10.4.13",
"eslint": "^8.57.1",
"eslint-config-prettier": "^9.1.0",
"eslint-config-standard-with-typescript": "^34.0.0",
"eslint-plugin-import": "^2.30.0",
"eslint-plugin-import": "^2.31.0",
"eslint-plugin-n": "^15.7.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-promise": "^6.6.0",
"eslint-plugin-react": "^7.35.0",
"eslint-plugin-react": "^7.37.2",
"eslint-plugin-unused-imports": "^4.1.4",
"husky": "^8.0.0",
"lint-staged": "^15.2.10",
"postcss": "^8.4.41",
"prettier": "^3.3.3",
"prettier-plugin-tailwindcss": "^0.6.8",
"tailwindcss": "^3.4.11",
"tailwindcss": "^3.4.15",
"typescript": "^5.6.2",
"vite": "^5.4.6",
"vite": "^5.4.11",
"vite-plugin-svgr": "^4.2.0"
}
}

View File

@@ -18,6 +18,7 @@ import SourceDropdown from './components/SourceDropdown';
import {
setConversation,
updateConversationId,
handleAbort,
} from './conversation/conversationSlice';
import ConversationTile from './conversation/ConversationTile';
import { useDarkTheme, useMediaQuery, useOutsideAlerter } from './hooks';
@@ -32,7 +33,6 @@ import {
selectConversations,
selectModalStateDeleteConv,
selectSelectedDocs,
selectSelectedDocsStatus,
selectSourceDocs,
selectPaginatedDocuments,
setConversations,
@@ -51,21 +51,7 @@ interface NavigationProps {
navOpen: boolean;
setNavOpen: React.Dispatch<React.SetStateAction<boolean>>;
}
/* const NavImage: React.FC<{
Light: string | undefined;
Dark: string | undefined;
}> = ({ Light, Dark }) => {
return (
<>
<img src={Dark} alt="icon" className="ml-2 hidden w-5 dark:block " />
<img src={Light} alt="icon" className="ml-2 w-5 dark:hidden filter dark:invert" />
</>
);
};
NavImage.propTypes = {
Light: PropTypes.string,
Dark: PropTypes.string,
}; */
export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
const dispatch = useDispatch();
const queries = useSelector(selectQueries);
@@ -85,10 +71,6 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
const [apiKeyModalState, setApiKeyModalState] =
useState<ActiveState>('INACTIVE');
const isSelectedDocsSet = useSelector(selectSelectedDocsStatus);
const [selectedDocsModalState, setSelectedDocsModalState] =
useState<ActiveState>(isSelectedDocsSet ? 'INACTIVE' : 'ACTIVE');
const [uploadModalState, setUploadModalState] =
useState<ActiveState>('INACTIVE');
@@ -180,6 +162,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
};
const resetConversation = () => {
handleAbort();
dispatch(setConversation([]));
dispatch(
updateConversationId({
@@ -491,11 +474,15 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
setModalState={setModalStateDeleteConv}
handleDeleteAllConv={handleDeleteAllConversations}
/>
<Upload
modalState={uploadModalState}
setModalState={setUploadModalState}
isOnboarding={false}
></Upload>
{uploadModalState === 'ACTIVE' && (
<Upload
receivedFile={[]}
setModalState={setUploadModalState}
isOnboarding={false}
renderTab={null}
close={() => setUploadModalState('INACTIVE')}
></Upload>
)}
</>
);
}

View File

@@ -0,0 +1,4 @@
<svg width="72" height="72" viewBox="0 0 72 72" fill="current" xmlns="http://www.w3.org/2000/svg">
<path d="M33.8788 9.87836C34.4413 9.31595 35.2043 9 35.9998 9C36.7952 9 37.5582 9.31595 38.1208 9.87836L50.1208 21.8784C50.6672 22.4442 50.9696 23.202 50.9628 23.9886C50.9559 24.7752 50.6404 25.5276 50.0842 26.0838C49.528 26.64 48.7755 26.9555 47.989 26.9624C47.2024 26.9692 46.4446 26.6668 45.8788 26.1204L38.9998 19.2414V47.9994C38.9998 48.795 38.6837 49.5581 38.1211 50.1207C37.5585 50.6833 36.7954 50.9994 35.9998 50.9994C35.2041 50.9994 34.441 50.6833 33.8784 50.1207C33.3158 49.5581 32.9998 48.795 32.9998 47.9994V19.2414L26.1208 26.1204C25.555 26.6668 24.7971 26.9692 24.0106 26.9624C23.224 26.9555 22.4715 26.64 21.9153 26.0838C21.3591 25.5276 21.0436 24.7752 21.0367 23.9886C21.0299 23.202 21.3323 22.4442 21.8788 21.8784L33.8788 9.87836Z" fill="#313131"/>
<path d="M18 51C18 50.2044 17.6839 49.4413 17.1213 48.8787C16.5587 48.3161 15.7956 48 15 48C14.2044 48 13.4413 48.3161 12.8787 48.8787C12.3161 49.4413 12 50.2044 12 51V52.8C12 58.446 16.554 63 22.2 63H49.8C55.446 63 60 58.446 60 52.8V51C60 50.2044 59.6839 49.4413 59.1213 48.8787C58.5587 48.3161 57.7957 48 57 48C56.2043 48 55.4413 48.3161 54.8787 48.8787C54.3161 49.4413 54 50.2044 54 51V52.8C54 55.134 52.134 57 49.8 57H22.2C19.866 57 18 55.134 18 52.8V51Z" fill="#313131"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 735 B

View File

@@ -5,7 +5,7 @@ export default function Avatar({
size,
className,
}: {
avatar: string | ReactNode;
avatar: ReactNode;
size?: 'SMALL' | 'MEDIUM' | 'LARGE';
className: string;
}) {

View File

@@ -19,7 +19,10 @@ const Pagination: React.FC<PaginationProps> = ({
onPageChange,
onRowsPerPageChange,
}) => {
const [rowsPerPageOptions] = useState([5, 10, 15, 20]);
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
const rowsPerPageOptions = [5, 10, 20, 50];
const toggleDropdown = () => setIsDropdownOpen((prev) => !prev);
const handlePreviousPage = () => {
if (currentPage > 1) {
@@ -41,31 +44,51 @@ const Pagination: React.FC<PaginationProps> = ({
onPageChange(totalPages);
};
const handleSelectRowsPerPage = (rows: number) => {
setIsDropdownOpen(false);
onRowsPerPageChange(rows);
};
return (
<div className="flex items-center text-xs justify-end gap-4 mt-2 p-2 border-gray-200">
<div className="flex items-center gap-2 ">
{/* 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>
<select
value={rowsPerPage}
onChange={(e) => onRowsPerPageChange(Number(e.target.value))}
className="border border-gray-300 rounded px-2 py-1 dark:bg-dark-charcoal dark:text-gray-50"
>
{rowsPerPageOptions.map((option) => (
<option
className="bg-white dark:bg-dark-charcoal dark:text-gray-50"
key={option}
value={option}
>
{option}
</option>
))}
</select>
<div className="relative">
<button
onClick={toggleDropdown}
className="px-3 py-1 border rounded dark:bg-dark-charcoal dark:text-light-gray hover:bg-gray-200 dark:hover:bg-neutral-700"
>
{rowsPerPage}
</button>
<div
className={`absolute z-50 right-0 mt-1 w-28 transform bg-white dark:bg-dark-charcoal shadow-lg ring-1 ring-black ring-opacity-5 transition-all duration-200 ease-in-out ${
isDropdownOpen
? 'scale-100 opacity-100 block'
: 'scale-95 opacity-0 hidden'
}`}
>
{rowsPerPageOptions.map((option) => (
<div
key={option}
onClick={() => handleSelectRowsPerPage(option)}
className={`cursor-pointer px-4 py-2 text-xs hover:bg-gray-100 dark:hover:bg-neutral-700 ${
rowsPerPage === option
? 'bg-gray-100 dark:bg-neutral-700 dark:text-light-gray'
: 'bg-white dark:bg-dark-charcoal dark:text-light-gray'
}`}
>
{option}
</div>
))}
</div>
</div>
</div>
{/* Pagination controls */}
<div className="text-gray-900 dark:text-gray-50">
Page {currentPage} of {totalPages}
</div>
<div className="flex items-center gap-2 text-gray-900 dark:text-gray-50">
<button
onClick={handleFirstPage}
@@ -74,7 +97,7 @@ const Pagination: React.FC<PaginationProps> = ({
>
<img
src={DoubleArrowLeft}
alt="arrow"
alt="First page"
className="dark:invert dark:sepia dark:brightness-200"
/>
</button>
@@ -85,7 +108,7 @@ const Pagination: React.FC<PaginationProps> = ({
>
<img
src={SingleArrowLeft}
alt="arrow"
alt="Previous page"
className="dark:invert dark:sepia dark:brightness-200"
/>
</button>
@@ -96,7 +119,7 @@ const Pagination: React.FC<PaginationProps> = ({
>
<img
src={SingleArrowRight}
alt="arrow"
alt="Next page"
className="dark:invert dark:sepia dark:brightness-200"
/>
</button>
@@ -107,7 +130,7 @@ const Pagination: React.FC<PaginationProps> = ({
>
<img
src={DoubleArrowRight}
alt="arrow"
alt="Last page"
className="dark:invert dark:sepia dark:brightness-200"
/>
</button>

View File

@@ -48,7 +48,7 @@ export default function DropdownMenu({
<div className="static inline-block text-left" ref={dropdownRef}>
<button
onClick={handleToggle}
className="flex w-20 cursor-pointer flex-row items-center gap-px rounded-3xl border-purple-30/25 bg-purple-30 p-2 text-xs text-white hover:bg-[#6F3FD1] focus:outline-none"
className="flex w-20 cursor-pointer flex-row gap-1 rounded-3xl border-purple-30/25 bg-purple-30 p-2 text-xs text-white hover:bg-[#6F3FD1] focus:outline-none"
>
{icon && <img src={icon} alt="OptionIcon" className="h-4 w-4" />}
{selectedOption.value !== 'never' ? selectedOption.label : name}

View File

@@ -1,8 +1,10 @@
import { Fragment, useEffect, useRef, useState } from 'react';
import { Fragment, useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import Hero from '../Hero';
import { useDropzone } from 'react-dropzone';
import DragFileUpload from '../assets/DragFileUpload.svg';
import ArrowDown from '../assets/arrow-down.svg';
import newChatIcon from '../assets/openNewChat.svg';
import Send from '../assets/send.svg';
@@ -21,12 +23,15 @@ import { FEEDBACK, Query } from './conversationModels';
import {
addQuery,
fetchAnswer,
resendQuery,
selectQueries,
selectStatus,
setConversation,
updateConversationId,
updateQuery,
} from './conversationSlice';
import Upload from '../upload/Upload';
import { ActiveState } from '../models/misc';
export default function Conversation() {
const queries = useSelector(selectQueries);
@@ -44,6 +49,47 @@ export default function Conversation() {
const [isShareModalOpen, setShareModalState] = useState<boolean>(false);
const { t } = useTranslation();
const { isMobile } = useMediaQuery();
const [uploadModalState, setUploadModalState] =
useState<ActiveState>('INACTIVE');
const [files, setFiles] = useState<File[]>([]);
const [handleDragActive, setHandleDragActive] = useState<boolean>(false);
const onDrop = useCallback((acceptedFiles: File[]) => {
setUploadModalState('ACTIVE');
setFiles(acceptedFiles);
setHandleDragActive(false);
}, []);
const { getRootProps, getInputProps } = useDropzone({
onDrop,
noClick: true,
multiple: true,
onDragEnter: () => {
setHandleDragActive(true);
},
onDragLeave: () => {
setHandleDragActive(false);
},
maxSize: 25000000,
accept: {
'application/pdf': ['.pdf'],
'text/plain': ['.txt'],
'text/x-rst': ['.rst'],
'text/x-markdown': ['.md'],
'application/zip': ['.zip'],
'application/vnd.openxmlformats-officedocument.wordprocessingml.document':
['.docx'],
'application/json': ['.json'],
'text/csv': ['.csv'],
'text/html': ['.html'],
'application/epub+zip': ['.epub'],
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': [
'.xlsx',
],
'application/vnd.openxmlformats-officedocument.presentationml.presentation':
['.pptx'],
},
});
const handleUserInterruption = () => {
if (!eventInterrupt && status === 'loading') setEventInterrupt(true);
@@ -85,27 +131,57 @@ export default function Conversation() {
const handleQuestion = ({
question,
isRetry = false,
updated = null,
indx = undefined,
}: {
question: string;
isRetry?: boolean;
updated?: boolean | null;
indx?: number;
}) => {
question = question.trim();
if (question === '') return;
setEventInterrupt(false);
!isRetry && dispatch(addQuery({ prompt: question })); //dispatch only new queries
fetchStream.current = dispatch(fetchAnswer({ question }));
if (updated === true) {
!isRetry &&
dispatch(resendQuery({ index: indx as number, prompt: question })); //dispatch only new queries
fetchStream.current = dispatch(fetchAnswer({ question, indx }));
} else {
question = question.trim();
if (question === '') return;
setEventInterrupt(false);
!isRetry && dispatch(addQuery({ prompt: question })); //dispatch only new queries
fetchStream.current = dispatch(fetchAnswer({ question }));
}
};
const handleFeedback = (query: Query, feedback: FEEDBACK, index: number) => {
const prevFeedback = query.feedback;
dispatch(updateQuery({ index, query: { feedback } }));
handleSendFeedback(query.prompt, query.response!, feedback).catch(() =>
dispatch(updateQuery({ index, query: { feedback: prevFeedback } })),
handleSendFeedback(
query.prompt,
query.response!,
feedback,
conversationId as string,
index,
).catch(() =>
handleSendFeedback(
query.prompt,
query.response!,
feedback,
conversationId as string,
index,
).catch(() =>
dispatch(updateQuery({ index, query: { feedback: prevFeedback } })),
),
);
};
const handleQuestionSubmission = () => {
if (inputRef.current?.value && status !== 'loading') {
const handleQuestionSubmission = (
updatedQuestion?: string,
updated?: boolean,
indx?: number,
) => {
if (updated === true) {
handleQuestion({ question: updatedQuestion as string, updated, indx });
} else if (inputRef.current?.value && status !== 'loading') {
if (lastQueryReturnedErr) {
// update last failed query with new prompt
dispatch(
@@ -290,6 +366,8 @@ export default function Conversation() {
key={`${index}QUESTION`}
message={query.prompt}
type="QUESTION"
handleUpdatedQuestionSubmission={handleQuestionSubmission}
questionNumber={index}
sources={query.sources}
></ConversationBubble>
@@ -303,8 +381,12 @@ export default function Conversation() {
)}
</div>
<div className="flex w-11/12 flex-col items-end self-center rounded-2xl bg-opacity-0 z-3 sm:w-[62%] h-auto">
<div className="flex w-full items-center rounded-[40px] border border-silver bg-white py-1 dark:bg-raisin-black">
<div className="flex w-11/12 flex-col items-end self-center rounded-2xl bg-opacity-0 z-3 sm:w-[62%] h-auto py-1">
<div
{...getRootProps()}
className="flex w-full items-center rounded-[40px] border border-silver bg-white dark:bg-raisin-black"
>
<input {...getInputProps()}></input>
<textarea
id="inputbox"
ref={inputRef}
@@ -328,7 +410,7 @@ export default function Conversation() {
<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 "
onClick={handleQuestionSubmission}
onClick={() => handleQuestionSubmission()}
src={isDarkTheme ? SendDark : Send}
></img>
</div>
@@ -339,6 +421,26 @@ export default function Conversation() {
{t('tagline')}
</p>
</div>
{handleDragActive && (
<div className="pointer-events-none fixed top-0 left-0 z-30 flex flex-col size-full items-center justify-center bg-opacity-50 bg-white dark:bg-gray-alpha">
<img className="filter dark:invert" src={DragFileUpload} />
<span className="px-2 text-2xl font-bold text-outer-space dark:text-silver">
{t('modals.uploadDoc.drag.title')}
</span>
<span className="p-2 text-s w-48 text-center text-outer-space dark:text-silver">
{t('modals.uploadDoc.drag.description')}
</span>
</div>
)}
{uploadModalState === 'ACTIVE' && (
<Upload
receivedFile={files}
setModalState={setUploadModalState}
isOnboarding={false}
renderTab={'file'}
close={() => setUploadModalState('INACTIVE')}
></Upload>
)}
</div>
);
}

View File

@@ -1,6 +1,6 @@
import 'katex/dist/katex.min.css';
import { forwardRef, useState } from 'react';
import { forwardRef, useRef, useState } from 'react';
import ReactMarkdown from 'react-markdown';
import { useSelector } from 'react-redux';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
@@ -15,6 +15,8 @@ import Document from '../assets/document.svg';
import Like from '../assets/like.svg?react';
import Link from '../assets/link.svg';
import Sources from '../assets/sources.svg';
import Edit from '../assets/edit.svg';
import UserIcon from '../assets/user.png';
import Avatar from '../components/Avatar';
import CopyButton from '../components/CopyButton';
import Sidebar from '../components/Sidebar';
@@ -25,6 +27,7 @@ import {
} from '../preferences/preferenceSlice';
import classes from './ConversationBubble.module.css';
import { FEEDBACK, MESSAGE_TYPE } from './conversationModels';
import { useOutsideAlerter } from '../hooks';
const DisableSourceFE = import.meta.env.VITE_DISABLE_SOURCE_FE || false;
@@ -38,36 +41,113 @@ const ConversationBubble = forwardRef<
handleFeedback?: (feedback: FEEDBACK) => void;
sources?: { title: string; text: string; source: string }[];
retryBtn?: React.ReactElement;
questionNumber?: number;
handleUpdatedQuestionSubmission?: (
updatedquestion?: string,
updated?: boolean,
index?: number,
) => void;
}
>(function ConversationBubble(
{ message, type, className, feedback, handleFeedback, sources, retryBtn },
{
message,
type,
className,
feedback,
handleFeedback,
sources,
retryBtn,
questionNumber,
handleUpdatedQuestionSubmission,
},
ref,
) {
// const bubbleRef = useRef<HTMLDivElement | null>(null);
const chunks = useSelector(selectChunks);
const selectedDocs = useSelector(selectSelectedDocs);
const [isLikeHovered, setIsLikeHovered] = useState(false);
const [isEditClicked, setIsEditClicked] = useState(false);
const [isDislikeHovered, setIsDislikeHovered] = useState(false);
const [isQuestionHovered, setIsQuestionHovered] = useState(false);
const [editInputBox, setEditInputBox] = useState<string>('');
const [isLikeClicked, setIsLikeClicked] = useState(false);
const [isDislikeClicked, setIsDislikeClicked] = useState(false);
const [activeTooltip, setActiveTooltip] = useState<number | null>(null);
const [isSidebarOpen, setIsSidebarOpen] = useState<boolean>(false);
const editableQueryRef = useRef<HTMLDivElement | null>(null);
useOutsideAlerter(editableQueryRef, () => setIsEditClicked(false), [], true);
const handleEditClick = () => {
setIsEditClicked(false);
handleUpdatedQuestionSubmission?.(editInputBox, true, questionNumber);
};
let bubble;
if (type === 'QUESTION') {
bubble = (
<div
ref={ref}
className={`flex flex-row-reverse self-end flex-wrap ${className}`}
onMouseEnter={() => setIsQuestionHovered(true)}
onMouseLeave={() => setIsQuestionHovered(false)}
>
<Avatar className="mt-2 text-2xl" avatar="🧑‍💻"></Avatar>
<div
style={{
wordBreak: 'break-word',
}}
className="ml-10 mr-2 flex items-center rounded-[28px] bg-purple-30 py-[14px] px-[19px] text-white max-w-full whitespace-pre-wrap leading-normal"
ref={ref}
className={`flex flex-row-reverse self-end flex-wrap ${className}`}
>
{message}
<Avatar
size="SMALL"
className="mt-2 text-2xl"
avatar={
<img className="rounded-full mr-1" width={30} src={UserIcon} />
}
/>
{!isEditClicked && (
<div
style={{
wordBreak: 'break-word',
}}
className="text-sm sm:text-base ml-2 mr-2 flex items-center rounded-[28px] bg-purple-30 py-[14px] px-[19px] text-white max-w-full whitespace-pre-wrap leading-normal"
>
{message}
</div>
)}
{isEditClicked && (
<div ref={editableQueryRef} className="w-[75%] flex flex-col">
<textarea
placeholder="Type the updated query..."
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"
/>
<div
className={`flex flex-row-reverse justify-end gap-1 mt-3 text-sm font-medium`}
>
<button
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
</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
</button>
</div>
</div>
)}
<button
onClick={() => {
setIsEditClicked(true);
setEditInputBox(message);
}}
className={`h-fit mt-3 p-2 cursor-pointer rounded-full hover:bg-[#35363B] flex items-center ${isQuestionHovered || isEditClicked ? 'visible' : 'invisible'}`}
>
<img src={Edit} alt="Edit" className="cursor-pointer" />
</button>
</div>
</div>
);
@@ -241,7 +321,7 @@ const ConversationBubble = forwardRef<
}`}
>
<ReactMarkdown
className="fade-in whitespace-pre-wrap break-normal leading-normal"
className="fade-in whitespace-pre-wrap break-words leading-normal"
remarkPlugins={[remarkGfm, remarkMath]}
rehypePlugins={[rehypeKatex]}
components={{
@@ -269,12 +349,7 @@ const ConversationBubble = forwardRef<
</div>
</div>
) : (
<code
className={className ? className : 'whitespace-pre-line'}
{...props}
>
{children}
</code>
<code className="whitespace-pre-line">{children}</code>
);
},
ul({ children }) {
@@ -361,7 +436,8 @@ const ConversationBubble = forwardRef<
feedback === 'LIKE' || type !== 'ERROR'
? 'group-hover:lg:visible'
: ''
}`}
}
${feedback === 'DISLIKE' && type !== 'ERROR' ? 'hidden' : ''}`}
>
<div>
<div
@@ -379,9 +455,15 @@ const ConversationBubble = forwardRef<
: 'fill-none stroke-gray-4000'
}`}
onClick={() => {
handleFeedback?.('LIKE');
setIsLikeClicked(true);
setIsDislikeClicked(false);
if (feedback === undefined || feedback === null) {
handleFeedback?.('LIKE');
setIsLikeClicked(true);
setIsDislikeClicked(false);
} else if (feedback === 'LIKE') {
handleFeedback?.(null);
setIsLikeClicked(false);
setIsDislikeClicked(false);
}
}}
onMouseEnter={() => setIsLikeHovered(true)}
onMouseLeave={() => setIsLikeHovered(false)}
@@ -396,7 +478,7 @@ const ConversationBubble = forwardRef<
feedback === 'DISLIKE' || type !== 'ERROR'
? 'group-hover:lg:visible'
: ''
}`}
} ${feedback === 'LIKE' && type !== 'ERROR' ? ' hidden' : ''} `}
>
<div>
<div
@@ -413,9 +495,15 @@ const ConversationBubble = forwardRef<
: 'fill-none stroke-gray-4000'
}`}
onClick={() => {
handleFeedback?.('DISLIKE');
setIsDislikeClicked(true);
setIsLikeClicked(false);
if (feedback === undefined || feedback === null) {
handleFeedback?.('DISLIKE');
setIsDislikeClicked(true);
setIsLikeClicked(false);
} else if (feedback === 'DISLIKE') {
handleFeedback?.(null);
setIsLikeClicked(false);
setIsDislikeClicked(false);
}
}}
onMouseEnter={() => setIsDislikeHovered(true)}
onMouseLeave={() => setIsDislikeHovered(false)}

View File

@@ -26,6 +26,7 @@ import {
selectQueries,
} from './sharedConversationSlice';
import { useSelector } from 'react-redux';
import { Helmet } from 'react-helmet';
export const SharedConversation = () => {
const navigate = useNavigate();
@@ -176,94 +177,111 @@ export const SharedConversation = () => {
}, []);
return (
<div className="flex h-full flex-col items-center justify-between gap-2 overflow-y-hidden dark:bg-raisin-black">
<div
ref={sharedConversationRef}
onWheel={handleUserInterruption}
onTouchMove={handleUserInterruption}
className="flex w-full justify-center overflow-auto"
>
<div className="mt-0 w-11/12 md:w-10/12 lg:w-6/12">
<div className="mb-2 w-full border-b pb-2 dark:border-b-silver">
<h1 className="font-semi-bold text-4xl text-chinese-black dark:text-chinese-silver">
{title}
</h1>
<h2 className="font-semi-bold text-base text-chinese-black dark:text-chinese-silver">
{t('sharedConv.subtitle')}{' '}
<a href="/" className="text-[#007DFF]">
DocsGPT
</a>
</h2>
<h2 className="font-semi-bold text-base text-chinese-black dark:text-chinese-silver">
{date}
</h2>
</div>
<div className="">
{queries?.map((query, index) => {
return (
<Fragment key={index}>
<ConversationBubble
ref={endMessageRef}
className={'mb-1 last:mb-28 md:mb-7'}
key={`${index}QUESTION`}
message={query.prompt}
type="QUESTION"
sources={query.sources}
></ConversationBubble>
<>
<Helmet>
<title>{`DocsGPT | ${title}`}</title>
<meta name="description" content="Shared conversations with DocsGPT" />
<meta property="og:title" content={title} />
<meta
property="og:description"
content="Shared conversations with DocsGPT"
/>
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta
name="twitter:description"
content="Shared conversations with DocsGPT"
/>
</Helmet>
<div className="flex h-full flex-col items-center justify-between gap-2 overflow-y-hidden dark:bg-raisin-black">
<div
ref={sharedConversationRef}
onWheel={handleUserInterruption}
onTouchMove={handleUserInterruption}
className="flex w-full justify-center overflow-auto"
>
<div className="mt-0 w-11/12 md:w-10/12 lg:w-6/12">
<div className="mb-2 w-full border-b pb-2 dark:border-b-silver">
<h1 className="font-semi-bold text-4xl text-chinese-black dark:text-chinese-silver">
{title}
</h1>
<h2 className="font-semi-bold text-base text-chinese-black dark:text-chinese-silver">
{t('sharedConv.subtitle')}{' '}
<a href="/" className="text-[#007DFF]">
DocsGPT
</a>
</h2>
<h2 className="font-semi-bold text-base text-chinese-black dark:text-chinese-silver">
{date}
</h2>
</div>
<div className="">
{queries?.map((query, index) => {
return (
<Fragment key={index}>
<ConversationBubble
ref={endMessageRef}
className={'mb-1 last:mb-28 md:mb-7'}
key={`${index}QUESTION`}
message={query.prompt}
type="QUESTION"
sources={query.sources}
></ConversationBubble>
{prepResponseView(query, index)}
</Fragment>
);
})}
{prepResponseView(query, index)}
</Fragment>
);
})}
</div>
</div>
</div>
</div>
<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
id="inputbox"
ref={inputRef}
tabIndex={1}
onPaste={handlePaste}
placeholder={t('inputPlaceholder')}
contentEditable
className={`inputbox-style max-h-24 w-full overflow-y-auto overflow-x-hidden whitespace-pre-wrap rounded-full bg-white pt-5 pb-[22px] text-base leading-tight opacity-100 focus:outline-none dark:bg-raisin-black dark:text-bright-gray`}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleQuestionSubmission();
}
}}
></div>
{status === 'loading' ? (
<img
src={Spinner}
className="relative right-[38px] bottom-[24px] -mr-[30px] animate-spin cursor-pointer self-end bg-transparent filter dark:invert"
></img>
) : (
<div className="mx-1 cursor-pointer rounded-full p-3 text-center hover:bg-gray-3000 dark:hover:bg-dark-charcoal">
<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
id="inputbox"
ref={inputRef}
tabIndex={1}
onPaste={handlePaste}
placeholder={t('inputPlaceholder')}
contentEditable
className={`inputbox-style max-h-24 w-full overflow-y-auto overflow-x-hidden whitespace-pre-wrap rounded-full bg-white pt-5 pb-[22px] text-base leading-tight opacity-100 focus:outline-none dark:bg-raisin-black dark:text-bright-gray`}
onKeyDown={(e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
handleQuestionSubmission();
}
}}
></div>
{status === 'loading' ? (
<img
onClick={handleQuestionSubmission}
className="ml-[4px] h-6 w-6 text-white filter dark:invert"
src={Send}
src={Spinner}
className="relative right-[38px] bottom-[24px] -mr-[30px] animate-spin cursor-pointer self-end bg-transparent filter dark:invert"
></img>
</div>
)}
</div>
) : (
<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"
>
{t('sharedConv.button')}
</button>
)}
<span className="mb-2 hidden text-xs text-dark-charcoal dark:text-silver sm:inline">
{t('sharedConv.meta')}
</span>
) : (
<div className="mx-1 cursor-pointer rounded-full p-3 text-center hover:bg-gray-3000 dark:hover:bg-dark-charcoal">
<img
onClick={handleQuestionSubmission}
className="ml-[4px] h-6 w-6 text-white filter dark:invert"
src={Send}
></img>
</div>
)}
</div>
) : (
<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"
>
{t('sharedConv.button')}
</button>
)}
<span className="mb-2 hidden text-xs text-dark-charcoal dark:text-silver sm:inline">
{t('sharedConv.meta')}
</span>
</div>
</div>
</div>
</>
);
};

View File

@@ -75,6 +75,7 @@ export function handleFetchAnswerSteaming(
chunks: string,
token_limit: number,
onEvent: (event: MessageEvent) => void,
indx?: number,
): Promise<Answer> {
history = history.map((item) => {
return { prompt: item.prompt, response: item.response };
@@ -87,6 +88,7 @@ export function handleFetchAnswerSteaming(
chunks: chunks,
token_limit: token_limit,
isNoneDoc: selectedDocs === null,
index: indx,
};
if (selectedDocs && 'id' in selectedDocs) {
payload.active_docs = selectedDocs.id as string;
@@ -200,12 +202,16 @@ export function handleSendFeedback(
prompt: string,
response: string,
feedback: FEEDBACK,
conversation_id: string,
prompt_index: number,
) {
return conversationService
.feedback({
question: prompt,
answer: response,
feedback: feedback,
conversation_id: conversation_id,
question_index: prompt_index,
})
.then((response) => {
if (response.ok) {

View File

@@ -1,6 +1,6 @@
export type MESSAGE_TYPE = 'QUESTION' | 'ANSWER' | 'ERROR';
export type Status = 'idle' | 'loading' | 'failed';
export type FEEDBACK = 'LIKE' | 'DISLIKE';
export type FEEDBACK = 'LIKE' | 'DISLIKE' | null;
export interface Message {
text: string;
@@ -41,4 +41,5 @@ export interface RetrievalPayload {
chunks: string;
token_limit: number;
isNoneDoc: boolean;
index?: number;
}

View File

@@ -17,133 +17,147 @@ const initialState: ConversationState = {
const API_STREAMING = import.meta.env.VITE_API_STREAMING === 'true';
export const fetchAnswer = createAsyncThunk<Answer, { question: string }>(
'fetchAnswer',
async ({ question }, { dispatch, getState, signal }) => {
let isSourceUpdated = false;
const state = getState() as RootState;
if (state.preference) {
if (API_STREAMING) {
await handleFetchAnswerSteaming(
question,
signal,
state.preference.selectedDocs!,
state.conversation.queries,
state.conversation.conversationId,
state.preference.prompt.id,
state.preference.chunks,
state.preference.token_limit,
let abortController: AbortController | null = null;
export function handleAbort() {
if (abortController) {
abortController.abort();
abortController = null;
}
}
(event) => {
const data = JSON.parse(event.data);
export const fetchAnswer = createAsyncThunk<
Answer,
{ question: string; indx?: number }
>('fetchAnswer', async ({ question, indx }, { dispatch, getState }) => {
if (abortController) {
abortController.abort();
}
abortController = new AbortController();
const { signal } = abortController;
if (data.type === 'end') {
dispatch(conversationSlice.actions.setStatus('idle'));
getConversations()
.then((fetchedConversations) => {
dispatch(setConversations(fetchedConversations));
})
.catch((error) => {
console.error('Failed to fetch conversations: ', error);
});
if (!isSourceUpdated) {
dispatch(
updateStreamingSource({
index: state.conversation.queries.length - 1,
query: { sources: [] },
}),
);
}
} else if (data.type === 'id') {
dispatch(
updateConversationId({
query: { conversationId: data.id },
}),
);
} else if (data.type === 'source') {
isSourceUpdated = true;
let isSourceUpdated = false;
const state = getState() as RootState;
if (state.preference) {
if (API_STREAMING) {
await handleFetchAnswerSteaming(
question,
signal,
state.preference.selectedDocs!,
state.conversation.queries,
state.conversation.conversationId,
state.preference.prompt.id,
state.preference.chunks,
state.preference.token_limit,
(event) => {
const data = JSON.parse(event.data);
if (data.type === 'end') {
dispatch(conversationSlice.actions.setStatus('idle'));
getConversations()
.then((fetchedConversations) => {
dispatch(setConversations(fetchedConversations));
})
.catch((error) => {
console.error('Failed to fetch conversations: ', error);
});
if (!isSourceUpdated) {
dispatch(
updateStreamingSource({
index: state.conversation.queries.length - 1,
query: { sources: data.source ?? [] },
}),
);
} else if (data.type === 'error') {
// set status to 'failed'
dispatch(conversationSlice.actions.setStatus('failed'));
dispatch(
conversationSlice.actions.raiseError({
index: state.conversation.queries.length - 1,
message: data.error,
}),
);
} else {
const result = data.answer;
dispatch(
updateStreamingQuery({
index: state.conversation.queries.length - 1,
query: { response: result },
index: indx ?? state.conversation.queries.length - 1,
query: { sources: [] },
}),
);
}
},
);
} else {
const answer = await handleFetchAnswer(
question,
signal,
state.preference.selectedDocs!,
state.conversation.queries,
state.conversation.conversationId,
state.preference.prompt.id,
state.preference.chunks,
state.preference.token_limit,
);
if (answer) {
let sourcesPrepped = [];
sourcesPrepped = answer.sources.map((source: { title: string }) => {
if (source && source.title) {
const titleParts = source.title.split('/');
return {
...source,
title: titleParts[titleParts.length - 1],
};
}
return source;
});
} else if (data.type === 'id') {
dispatch(
updateConversationId({
query: { conversationId: data.id },
}),
);
} else if (data.type === 'source') {
isSourceUpdated = true;
dispatch(
updateStreamingSource({
index: indx ?? state.conversation.queries.length - 1,
query: { sources: data.source ?? [] },
}),
);
} else if (data.type === 'error') {
// set status to 'failed'
dispatch(conversationSlice.actions.setStatus('failed'));
dispatch(
conversationSlice.actions.raiseError({
index: indx ?? state.conversation.queries.length - 1,
message: data.error,
}),
);
} else {
const result = data.answer;
dispatch(
updateStreamingQuery({
index: indx ?? state.conversation.queries.length - 1,
query: { response: result },
}),
);
}
},
indx,
);
} else {
const answer = await handleFetchAnswer(
question,
signal,
state.preference.selectedDocs!,
state.conversation.queries,
state.conversation.conversationId,
state.preference.prompt.id,
state.preference.chunks,
state.preference.token_limit,
);
if (answer) {
let sourcesPrepped = [];
sourcesPrepped = answer.sources.map((source: { title: string }) => {
if (source && source.title) {
const titleParts = source.title.split('/');
return {
...source,
title: titleParts[titleParts.length - 1],
};
}
return source;
});
dispatch(
updateQuery({
index: state.conversation.queries.length - 1,
query: { response: answer.answer, sources: sourcesPrepped },
}),
);
dispatch(
updateConversationId({
query: { conversationId: answer.conversationId },
}),
);
dispatch(conversationSlice.actions.setStatus('idle'));
getConversations()
.then((fetchedConversations) => {
dispatch(setConversations(fetchedConversations));
})
.catch((error) => {
console.error('Failed to fetch conversations: ', error);
});
}
dispatch(
updateQuery({
index: indx ?? state.conversation.queries.length - 1,
query: { response: answer.answer, sources: sourcesPrepped },
}),
);
dispatch(
updateConversationId({
query: { conversationId: answer.conversationId },
}),
);
dispatch(conversationSlice.actions.setStatus('idle'));
getConversations()
.then((fetchedConversations) => {
dispatch(setConversations(fetchedConversations));
})
.catch((error) => {
console.error('Failed to fetch conversations: ', error);
});
}
}
return {
conversationId: null,
title: null,
answer: '',
query: question,
result: '',
sources: [],
};
},
);
}
return {
conversationId: null,
title: null,
answer: '',
query: question,
result: '',
sources: [],
};
});
export const conversationSlice = createSlice({
name: 'conversation',
@@ -155,6 +169,15 @@ export const conversationSlice = createSlice({
setConversation(state, action: PayloadAction<Query[]>) {
state.queries = action.payload;
},
resendQuery(
state,
action: PayloadAction<{ index: number; prompt: string; query?: Query }>,
) {
state.queries = [
...state.queries.splice(0, action.payload.index),
action.payload,
];
},
updateStreamingQuery(
state,
action: PayloadAction<{ index: number; query: Partial<Query> }>,
@@ -236,6 +259,7 @@ export const selectStatus = (state: RootState) => state.conversation.status;
export const {
addQuery,
updateQuery,
resendQuery,
updateStreamingQuery,
updateConversationId,
updateStreamingSource,

View File

@@ -50,19 +50,23 @@ body.dark {
@layer components {
.table-default {
@apply block w-full mx-auto table-auto content-start justify-center rounded-xl border border-silver dark:border-silver/40 text-center dark:text-bright-gray overflow-auto;
@apply block w-full table-auto content-start justify-center rounded-xl border border-silver dark:border-silver/40 text-center dark:text-bright-gray overflow-auto;
}
.table-default th {
@apply p-4 w-full font-normal text-gray-400 text-nowrap; /* Remove border-r */
@apply p-4 font-normal text-gray-400 text-nowrap; /* Remove border-r */
}
.table-default th {
flex: 1;
}
.table-default th:last-child {
@apply w-[auto];
flex: 0; /* Ensure the last column does not stretch unnecessarily */
}
.table-default td {
@apply border-t border-silver dark:border-silver/40 px-4 py-2; /* Remove border-r */
@apply border-t w-full border-silver dark:border-silver/40 px-4 py-2; /* Remove border-r */
}
.table-default td:last-child {

View File

@@ -102,6 +102,10 @@
"agent": "User agent",
"searchQueries": "Search queries",
"numberOfPosts": "Number of posts"
},
"drag": {
"title": "Upload a source file",
"description": "Drop your file here to add it as a source"
}
},
"createAPIKey": {

View File

@@ -99,6 +99,10 @@
"agent": "Agente de Usuario",
"searchQueries": "Consultas de Búsqueda",
"numberOfPosts": "Número de publicaciones"
},
"drag": {
"title": "Cargar un archivo fuente",
"description": "Suelta tu archivo aquí para agregarlo como fuente."
}
},
"createAPIKey": {

View File

@@ -7,6 +7,7 @@ import es from './es.json'; //Spanish
import jp from './jp.json'; //Japanese
import zh from './zh.json'; //Mandarin
import zhTW from './zh-TW.json'; //Traditional Chinese
import ru from './ru.json'; //Russian
i18n
.use(LanguageDetector)
@@ -25,9 +26,12 @@ i18n
zh: {
translation: zh,
},
'zh-TW': {
zhTW: {
translation: zhTW,
},
ru: {
translation: ru,
},
},
fallbackLng: 'en',
detection: {

View File

@@ -99,6 +99,10 @@
"agent": "ユーザーエージェント",
"searchQueries": "検索クエリ",
"numberOfPosts": "投稿数"
},
"drag": {
"title": "ソースファイルをアップロードする",
"description": "ファイルをここにドロップしてソースとして追加します"
}
},
"createAPIKey": {

142
frontend/src/locale/ru.json Normal file
View File

@@ -0,0 +1,142 @@
{
"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": "Журналы"
}
},
"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": "Разрешить пользователям запрашивать дальнейшие действия"
}
},
"sharedConv": {
"subtitle": "Создано с помощью",
"button": "Начать работу с DocsGPT",
"meta": "DocsGPT использует GenAI, пожалуйста, проверьте важную информацию с помощью источников."
},
"convTile": {
"share": "Поделиться",
"delete": "Удалить",
"rename": "Переименовать",
"deleteWarning": "Вы уверены, что хотите удалить этот разговор?"
}
}

View File

@@ -92,6 +92,10 @@
"agent": "使用者代理(User-Agent)",
"searchQueries": "搜尋查詢",
"numberOfPosts": "貼文數量"
},
"drag": {
"title": "上傳原始檔",
"description": "將您的文件拖放到此處以將其添加為來源"
}
},
"createAPIKey": {

View File

@@ -99,6 +99,10 @@
"agent": "用户代理",
"searchQueries": "搜索查询",
"numberOfPosts": "帖子数量"
},
"drag": {
"title": "上传源文件",
"description": "将您的文件拖放到此处以将其添加为源"
}
},
"createAPIKey": {

View File

@@ -1,6 +1,6 @@
import Exit from '../assets/exit.svg';
import { ActiveState } from '../models/misc';
import { useTranslation } from 'react-i18next';
import WrapperModal from './WrapperModal';
function ConfirmationModal({
message,
modalState,
@@ -20,49 +20,43 @@ function ConfirmationModal({
}) {
const { t } = useTranslation();
return (
<article
className={`${
modalState === 'ACTIVE' ? 'visible' : 'hidden'
} fixed top-0 left-0 z-30 h-screen w-screen bg-gray-alpha`}
>
<article className="mx-auto mt-[35vh] flex w-[90vw] max-w-lg flex-col gap-4 rounded-2xl bg-white shadow-lg dark:bg-outer-space">
<div className="relative">
<button
className="absolute top-3 right-4 m-2 w-3"
onClick={() => {
setModalState('INACTIVE');
handleCancel && handleCancel();
}}
>
<img className="filter dark:invert" src={Exit} />
</button>
<div className="p-8">
<p className="font-base mb-1 w-[90%] text-lg text-jet dark:text-bright-gray">
{message}
</p>
<div>
<div className="mt-6 flex flex-row-reverse gap-1">
<button
onClick={handleSubmit}
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]"
>
{submitLabel}
</button>
<button
onClick={() => {
setModalState('INACTIVE');
handleCancel && handleCancel();
}}
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"
>
{cancelLabel ? cancelLabel : t('cancel')}
</button>
<>
{modalState === 'ACTIVE' && (
<WrapperModal
close={() => {
setModalState('INACTIVE');
handleCancel && handleCancel();
}}
>
<div className="relative">
<div className="p-8">
<p className="font-base mb-1 w-[90%] text-lg text-jet dark:text-bright-gray">
{message}
</p>
<div>
<div className="mt-6 flex flex-row-reverse gap-1">
<button
onClick={handleSubmit}
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]"
>
{submitLabel}
</button>
<button
onClick={() => {
setModalState('INACTIVE');
handleCancel && handleCancel();
}}
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"
>
{cancelLabel ? cancelLabel : t('cancel')}
</button>
</div>
</div>
</div>
</div>
</div>
</article>
</article>
</WrapperModal>
)}
</>
);
}

View File

@@ -3,11 +3,11 @@ import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import userService from '../api/services/userService';
import Exit from '../assets/exit.svg';
import Dropdown from '../components/Dropdown';
import Input from '../components/Input';
import { CreateAPIKeyModalProps, Doc } from '../models/misc';
import { selectSourceDocs } from '../preferences/preferenceSlice';
import WrapperModal from './WrapperModal';
const embeddingsName =
import.meta.env.VITE_EMBEDDINGS_NAME ||
@@ -73,91 +73,82 @@ export default function CreateAPIKeyModal({
handleFetchPrompts();
}, []);
return (
<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 className="relative w-11/12 rounded-2xl bg-white p-10 dark:bg-outer-space sm:w-[512px]">
<button className="absolute top-3 right-4 m-2 w-3" onClick={close}>
<img className="filter dark:invert" src={Exit} />
</button>
<div className="mb-6">
<span className="text-xl text-jet dark:text-bright-gray">
{t('modals.createAPIKey.label')}
</span>
</div>
<div className="relative mt-5 mb-4">
<span className="absolute left-2 -top-2 bg-white px-2 text-xs text-gray-4000 dark:bg-outer-space dark:text-silver">
{t('modals.createAPIKey.apiKeyName')}
</span>
<Input
type="text"
className="rounded-md"
value={APIKeyName}
onChange={(e) => setAPIKeyName(e.target.value)}
></Input>
</div>
<div className="my-4">
<Dropdown
placeholder={t('modals.createAPIKey.sourceDoc')}
selectedValue={sourcePath ? sourcePath.name : null}
onSelect={(selection: {
name: string;
id: string;
type: string;
}) => {
setSourcePath(selection);
}}
options={extractDocPaths()}
size="w-full"
rounded="xl"
border="border"
/>
</div>
<div className="my-4">
<Dropdown
options={activePrompts}
selectedValue={prompt ? prompt.name : null}
placeholder={t('modals.createAPIKey.prompt')}
onSelect={(value: { name: string; id: string; type: string }) =>
setPrompt(value)
}
size="w-full"
border="border"
/>
</div>
<div className="my-4">
<p className="mb-2 ml-2 font-semibold text-jet dark:text-bright-gray">
{t('modals.createAPIKey.chunks')}
</p>
<Dropdown
options={chunkOptions}
selectedValue={chunk}
onSelect={(value: string) => setChunk(value)}
size="w-full"
border="border"
/>
</div>
<button
disabled={!sourcePath || APIKeyName.length === 0 || !prompt}
onClick={() => {
if (sourcePath && prompt) {
const payload: any = {
name: APIKeyName,
prompt_id: prompt.id,
chunks: chunk,
};
if (sourcePath.type === 'default') {
payload.retriever = sourcePath.id;
}
if (sourcePath.type === 'local') {
payload.source = sourcePath.id;
}
createAPIKey(payload);
}
}}
className="float-right mt-4 rounded-full bg-purple-30 px-5 py-2 text-sm text-white hover:bg-[#6F3FD1] disabled:opacity-50"
>
{t('modals.createAPIKey.create')}
</button>
<WrapperModal close={close}>
<div className="mb-6">
<span className="text-xl text-jet dark:text-bright-gray">
{t('modals.createAPIKey.label')}
</span>
</div>
</div>
<div className="relative mt-5 mb-4">
<span className="absolute left-2 -top-2 bg-white px-2 text-xs text-gray-4000 dark:bg-outer-space dark:text-silver">
{t('modals.createAPIKey.apiKeyName')}
</span>
<Input
type="text"
className="rounded-md"
value={APIKeyName}
onChange={(e) => setAPIKeyName(e.target.value)}
></Input>
</div>
<div className="my-4">
<Dropdown
placeholder={t('modals.createAPIKey.sourceDoc')}
selectedValue={sourcePath ? sourcePath.name : null}
onSelect={(selection: { name: string; id: string; type: string }) => {
setSourcePath(selection);
}}
options={extractDocPaths()}
size="w-full"
rounded="xl"
border="border"
/>
</div>
<div className="my-4">
<Dropdown
options={activePrompts}
selectedValue={prompt ? prompt.name : null}
placeholder={t('modals.createAPIKey.prompt')}
onSelect={(value: { name: string; id: string; type: string }) =>
setPrompt(value)
}
size="w-full"
border="border"
/>
</div>
<div className="my-4">
<p className="mb-2 ml-2 font-semibold text-jet dark:text-bright-gray">
{t('modals.createAPIKey.chunks')}
</p>
<Dropdown
options={chunkOptions}
selectedValue={chunk}
onSelect={(value: string) => setChunk(value)}
size="w-full"
border="border"
/>
</div>
<button
disabled={!sourcePath || APIKeyName.length === 0 || !prompt}
onClick={() => {
if (sourcePath && prompt) {
const payload: any = {
name: APIKeyName,
prompt_id: prompt.id,
chunks: chunk,
};
if (sourcePath.type === 'default') {
payload.retriever = sourcePath.id;
}
if (sourcePath.type === 'local') {
payload.source = sourcePath.id;
}
createAPIKey(payload);
}
}}
className="float-right mt-4 rounded-full bg-purple-30 px-5 py-2 text-sm text-white hover:bg-[#6F3FD1] disabled:opacity-50"
>
{t('modals.createAPIKey.create')}
</button>
</WrapperModal>
);
}

View File

@@ -10,7 +10,6 @@ import {
import Dropdown from '../components/Dropdown';
import { Doc } from '../models/misc';
import Spinner from '../assets/spinner.svg';
import Exit from '../assets/exit.svg';
const apiHost = import.meta.env.VITE_API_HOST || 'https://docsapi.arc53.com';
const embeddingsName =
import.meta.env.VITE_EMBEDDINGS_NAME ||
@@ -19,6 +18,7 @@ const embeddingsName =
type StatusType = 'loading' | 'idle' | 'fetched' | 'failed';
import conversationService from '../api/services/conversationService';
import WrapperModal from './WrapperModal';
export const ShareConversationModal = ({
close,
@@ -99,85 +99,78 @@ export const ShareConversationModal = ({
};
return (
<div className="fixed top-0 left-0 z-40 flex h-screen w-screen cursor-default items-center justify-center bg-gray-alpha bg-opacity-50 text-chinese-black dark:text-silver">
<div className="relative w-11/12 rounded-2xl bg-white p-10 dark:bg-outer-space sm:w-[512px]">
<button className="absolute top-3 right-4 m-2 w-3" onClick={close}>
<img className="filter dark:invert" src={Exit} />
</button>
<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>
<div className="flex items-center justify-between">
<span className="text-lg">{t('modals.shareConv.option')}</span>
<label className=" cursor-pointer select-none items-center">
<div className="relative">
<input
type="checkbox"
checked={allowPrompt}
onChange={togglePromptPermission}
className="sr-only"
/>
<div
className={`box block h-8 w-14 rounded-full border border-purple-30 ${
allowPrompt
? 'bg-purple-30 dark:bg-purple-30'
: 'dark:bg-transparent'
}`}
></div>
<div
className={`absolute left-1 top-1 flex h-6 w-6 items-center justify-center rounded-full transition ${
allowPrompt ? 'translate-x-full bg-silver' : 'bg-purple-30'
}`}
></div>
</div>
</label>
</div>
{allowPrompt && (
<div className="my-4">
<Dropdown
placeholder={t('modals.createAPIKey.sourceDoc')}
selectedValue={sourcePath}
onSelect={(selection: { label: string; value: string }) =>
setSourcePath(selection)
}
options={extractDocPaths(sourceDocs ?? [])}
size="w-full"
rounded="xl"
<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>
<div className="flex items-center justify-between">
<span className="text-lg">{t('modals.shareConv.option')}</span>
<label className=" cursor-pointer select-none items-center">
<div className="relative">
<input
type="checkbox"
checked={allowPrompt}
onChange={togglePromptPermission}
className="sr-only"
/>
<div
className={`box block h-8 w-14 rounded-full border border-purple-30 ${
allowPrompt
? 'bg-purple-30 dark:bg-purple-30'
: 'dark:bg-transparent'
}`}
></div>
<div
className={`absolute left-1 top-1 flex h-6 w-6 items-center justify-center rounded-full transition ${
allowPrompt ? 'translate-x-full bg-silver' : 'bg-purple-30'
}`}
></div>
</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">
{`${domain}/share/${identifier ?? '....'}`}
</span>
{status === 'fetched' ? (
<button
className="my-1 h-10 w-28 rounded-full border border-solid bg-purple-30 p-2 text-sm text-white hover:bg-[#6F3FD1]"
onClick={() => handleCopyKey(`${domain}/share/${identifier}`)}
>
{isCopied
? t('modals.saveKey.copied')
: t('modals.saveKey.copy')}
</button>
) : (
<button
className="my-1 flex h-10 w-28 items-center justify-evenly rounded-full border border-solid bg-purple-30 p-2 text-center text-sm font-normal text-white hover:bg-[#6F3FD1]"
onClick={() => {
shareCoversationPublicly(allowPrompt);
}}
>
{t('modals.shareConv.create')}
{status === 'loading' && (
<img
src={Spinner}
className="inline animate-spin cursor-pointer bg-transparent filter dark:invert"
></img>
)}
</button>
)}
</label>
</div>
{allowPrompt && (
<div className="my-4">
<Dropdown
placeholder={t('modals.createAPIKey.sourceDoc')}
selectedValue={sourcePath}
onSelect={(selection: { label: string; value: string }) =>
setSourcePath(selection)
}
options={extractDocPaths(sourceDocs ?? [])}
size="w-full"
rounded="xl"
/>
</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">
{`${domain}/share/${identifier ?? '....'}`}
</span>
{status === 'fetched' ? (
<button
className="my-1 h-10 w-28 rounded-full border border-solid bg-purple-30 p-2 text-sm text-white hover:bg-[#6F3FD1]"
onClick={() => handleCopyKey(`${domain}/share/${identifier}`)}
>
{isCopied ? t('modals.saveKey.copied') : t('modals.saveKey.copy')}
</button>
) : (
<button
className="my-1 flex h-10 w-28 items-center justify-evenly rounded-full border border-solid bg-purple-30 p-2 text-center text-sm font-normal text-white hover:bg-[#6F3FD1]"
onClick={() => {
shareCoversationPublicly(allowPrompt);
}}
>
{t('modals.shareConv.create')}
{status === 'loading' && (
<img
src={Spinner}
className="inline animate-spin cursor-pointer bg-transparent filter dark:invert"
></img>
)}
</button>
)}
</div>
</div>
</div>
</WrapperModal>
);
};

View File

@@ -0,0 +1,55 @@
import React, { useEffect, useRef } from 'react';
import { WrapperModalProps } from './types';
import Exit from '../assets/exit.svg';
const WrapperModal: React.FC<WrapperModalProps> = ({
children,
close,
isPerformingTask,
}) => {
const modalRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (isPerformingTask) return;
const handleClickOutside = (event: MouseEvent) => {
if (
modalRef.current &&
!modalRef.current.contains(event.target as Node)
) {
close();
}
};
const handleEscapePress = (event: KeyboardEvent) => {
if (event.key === 'Escape') {
close();
}
};
document.addEventListener('mousedown', handleClickOutside);
document.addEventListener('keydown', handleEscapePress);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
document.removeEventListener('keydown', handleEscapePress);
};
}, [close]);
return (
<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]"
>
{!isPerformingTask && (
<button className="absolute top-3 right-4 m-2 w-3" onClick={close}>
<img className="filter dark:invert" src={Exit} />
</button>
)}
{children}
</div>
</div>
);
};
export default WrapperModal;

View File

@@ -8,4 +8,9 @@ export type AvailableTool = {
description: string;
parameters: object;
}[];
export type WrapperModalProps = {
children?: React.ReactNode;
isPerformingTask?: boolean;
close: () => void;
};

View File

@@ -25,9 +25,10 @@ export async function getDocsWithPagination(
order = 'desc',
pageNumber = 1,
rowsPerPage = 10,
searchTerm = '',
): Promise<GetDocsResponse | null> {
try {
const query = `sort=${sort}&order=${order}&page=${pageNumber}&rows=${rowsPerPage}`;
const query = `sort=${sort}&order=${order}&page=${pageNumber}&rows=${rowsPerPage}&search=${searchTerm}`;
const response = await userService.getDocsWithPagination(query);
const data = await response.json();
const docs: Doc[] = [];

View File

@@ -109,41 +109,56 @@ export default function APIKeys() {
{loading ? (
<SkeletonLoader count={1} component={'chatbot'} />
) : (
<table className="table-default">
<thead>
<tr>
<th>{t('settings.apiKeys.name')}</th>
<th>{t('settings.apiKeys.sourceDoc')}</th>
<th>{t('settings.apiKeys.key')}</th>
<th></th>
</tr>
</thead>
<tbody>
{!apiKeys?.length && (
<tr>
<td colSpan={4} className="!p-4">
{t('settings.apiKeys.noData')}
</td>
</tr>
)}
{apiKeys?.map((element, index) => (
<tr key={index}>
<td>{element.name}</td>
<td>{element.source}</td>
<td>{element.key}</td>
<td>
<img
src={Trash}
alt="Delete"
className="h-4 w-4 cursor-pointer hover:opacity-50"
id={`img-${index}`}
onClick={() => handleDeleteKey(element.id)}
/>
</td>
</tr>
))}
</tbody>
</table>
<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 ">
<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">
{t('settings.apiKeys.sourceDoc')}
</th>
<th className="p-2">{t('settings.apiKeys.key')}</th>
<th></th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 dark:divide-neutral-700">
{!apiKeys?.length && (
<tr>
<td
colSpan={4}
className="!p-4 text-gray-800 dark:text-neutral-200 text-center"
>
{t('settings.apiKeys.noData')}
</td>
</tr>
)}
{Array.isArray(apiKeys) &&
apiKeys?.map((element, index) => (
<tr
key={index}
className="text-nowrap whitespace-nowrap text-center text-sm font-medium text-gray-800 dark:text-neutral-200 p-2"
>
<td className="p-1">{element.name}</td>
<td className="p-2">{element.source}</td>
<td>{element.key}</td>
<td>
<img
src={Trash}
alt="Delete"
className="h-4 w-4 cursor-pointer hover:opacity-50"
id={`img-${index}`}
onClick={() => handleDeleteKey(element.id)}
/>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
)}
</div>
</div>

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react';
import React, { useState, useEffect, useCallback } from 'react';
import PropTypes from 'prop-types';
import userService from '../api/services/userService';
import SyncIcon from '../assets/sync.svg';
@@ -15,6 +15,7 @@ import { Doc, DocumentsProps, ActiveState } from '../models/misc'; // Ensure Act
import { getDocs, getDocsWithPagination } from '../preferences/preferenceApi';
import { setSourceDocs } from '../preferences/preferenceSlice';
import { setPaginatedDocuments } from '../preferences/preferenceSlice';
import { truncate } from '../utils/stringUtils';
// Utility function to format numbers
const formatTokens = (tokens: number): string => {
@@ -40,25 +41,18 @@ const Documents: React.FC<DocumentsProps> = ({
const { t } = useTranslation();
const dispatch = useDispatch();
// State for search input
const [searchTerm, setSearchTerm] = useState('');
const [searchTerm, setSearchTerm] = useState<string>('');
// State for modal: active/inactive
const [modalState, setModalState] = useState<ActiveState>('INACTIVE'); // Initialize with inactive state
const [isOnboarding, setIsOnboarding] = useState(false); // State for onboarding flag
const [loading, setLoading] = useState(false);
const [isOnboarding, setIsOnboarding] = useState<boolean>(false); // State for onboarding flag
const [loading, setLoading] = useState<boolean>(false);
const [sortField, setSortField] = useState<'date' | 'tokens'>('date');
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
// Pagination
const [currentPage, setCurrentPage] = useState<number>(1);
const [rowsPerPage, setRowsPerPage] = useState<number>(10);
const [totalPages, setTotalPages] = useState<number>(1);
// const [totalDocuments, setTotalDocuments] = useState<number>(0);
// Filter documents based on the search term
const filteredDocuments = paginatedDocuments?.filter((document) =>
document.name.toLowerCase().includes(searchTerm.toLowerCase()),
);
// State for documents
const currentDocuments = filteredDocuments ?? [];
console.log('currentDocuments', currentDocuments);
const currentDocuments = paginatedDocuments ?? [];
const syncOptions = [
{ label: 'Never', value: 'never' },
{ label: 'Daily', value: 'daily' },
@@ -66,36 +60,50 @@ const Documents: React.FC<DocumentsProps> = ({
{ label: 'Monthly', value: 'monthly' },
];
const refreshDocs = (
field: 'date' | 'tokens' | undefined,
pageNumber?: number,
rows?: number,
) => {
const page = pageNumber ?? currentPage;
const rowsPerPg = rows ?? rowsPerPage;
const refreshDocs = useCallback(
(
field: 'date' | 'tokens' | undefined,
pageNumber?: number,
rows?: number,
) => {
const page = pageNumber ?? currentPage;
const rowsPerPg = rows ?? rowsPerPage;
if (field !== undefined) {
if (field === sortField) {
// Toggle sort order
setSortOrder(sortOrder === 'asc' ? 'desc' : 'asc');
} else {
// Change sort field and reset order to 'desc'
setSortField(field);
setSortOrder('desc');
// If field is undefined, (Pagination or Search) use the current sortField
const newSortField = field ?? sortField;
// If field is undefined, (Pagination or Search) use the current sortOrder
const newSortOrder =
field === sortField
? sortOrder === 'asc'
? 'desc'
: 'asc'
: sortOrder;
// If field is defined, update the sortField and sortOrder
if (field) {
setSortField(newSortField);
setSortOrder(newSortOrder);
}
}
getDocsWithPagination(sortField, sortOrder, page, rowsPerPg)
.then((data) => {
//dispatch(setSourceDocs(data ? data.docs : []));
dispatch(setPaginatedDocuments(data ? data.docs : []));
setTotalPages(data ? data.totalPages : 0);
//setTotalDocuments(data ? data.totalDocuments : 0);
})
.catch((error) => console.error(error))
.finally(() => {
setLoading(false);
});
};
setLoading(true);
getDocsWithPagination(
newSortField,
newSortOrder,
page,
rowsPerPg,
searchTerm,
)
.then((data) => {
dispatch(setPaginatedDocuments(data ? data.docs : []));
setTotalPages(data ? data.totalPages : 0);
})
.catch((error) => console.error(error))
.finally(() => {
setLoading(false);
});
},
[currentPage, rowsPerPage, sortField, sortOrder, searchTerm],
);
const handleManageSync = (doc: Doc, sync_frequency: string) => {
setLoading(true);
@@ -130,7 +138,12 @@ const Documents: React.FC<DocumentsProps> = ({
if (modalState === 'INACTIVE') {
refreshDocs(sortField, currentPage, rowsPerPage);
}
}, [modalState, sortField, currentPage, rowsPerPage]);
}, [modalState]);
useEffect(() => {
// undefine to prevent reset the sort order
refreshDocs(undefined, 1, rowsPerPage);
}, [searchTerm]);
return (
<div className="mt-8">
@@ -145,11 +158,18 @@ const Documents: React.FC<DocumentsProps> = ({
type="text"
id="document-search-input"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)} // Handle search input change
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"
onClick={() => {
setIsOnboarding(false); // Set onboarding flag if needed
setModalState('ACTIVE'); // Open the upload modal
@@ -161,131 +181,160 @@ const Documents: React.FC<DocumentsProps> = ({
{loading ? (
<SkeletonLoader count={1} />
) : (
<table className="table-default">
<thead>
<tr>
<th>{t('settings.documents.name')}</th>
<th>
<div className="flex justify-center items-center">
{t('settings.documents.date')}
<img
className="cursor-pointer"
onClick={() => refreshDocs('date')}
src={caretSort}
alt="sort"
/>
</div>
</th>
<th>
<div className="flex justify-center items-center">
{t('settings.documents.tokenUsage')}
<img
className="cursor-pointer"
onClick={() => refreshDocs('tokens')}
src={caretSort}
alt="sort"
/>
</div>
</th>
<th>
<div className="flex justify-center items-center">
{t('settings.documents.type')}
</div>
</th>
<th></th>
</tr>
</thead>
<tbody>
{!currentDocuments?.length && (
<tr>
<td colSpan={5} className="!p-4">
{t('settings.documents.noData')}
</td>
</tr>
)}
{Array.isArray(currentDocuments) &&
currentDocuments.map((document, index) => (
<tr key={index} className="text-nowrap font-normal">
<td>{document.name}</td>
<td>{document.date}</td>
<td>
{document.tokens ? formatTokens(+document.tokens) : ''}
</td>
<td>
{document.type === 'remote' ? 'Pre-loaded' : 'Private'}
</td>
<td>
<div className="w-full flex flex-row-reverse items-center justify-items-end ml-auto gap-2">
{document.type !== 'remote' && (
<button
className="h-8 w-8 border border-[#747474] rounded-full p-2 opacity-60 hover:opacity-100"
onClick={(event) => {
event.stopPropagation();
handleDeleteDocument(index, document);
}}
<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 ">
<thead>
<tr className="text-nowrap">
<th className="px-5 py-3 text-start font-medium text-gray-700 dark:text-gray-50 uppercase w-96">
{t('settings.documents.name')}
</th>
<th className="px-5 py-3 text-start font-medium text-gray-700 dark:text-gray-50 uppercase">
<div className="flex justify-center items-center">
{t('settings.documents.date')}
<img
className="cursor-pointer"
onClick={() => refreshDocs('date')}
src={caretSort}
alt="sort"
/>
</div>
</th>
<th
scope="col"
className="px-5 py-2 text-center font-medium text-gray-700 dark:text-gray-50 uppercase"
>
<div className="flex justify-center items-center">
{t('settings.documents.tokenUsage')}
<img
className="cursor-pointer"
onClick={() => refreshDocs('tokens')}
src={caretSort}
alt="sort"
/>
</div>
</th>
{/*}
<th className="px-5 py-2 text-start text-sm font-medium text-gray-700 dark:text-gray-50 uppercase">
<div className="flex justify-center items-center">
{t('settings.documents.type')}
</div>
</th>
*/}
<th
scope="col"
className="px-6 py-2 text-start font-medium text-gray-700 dark:text-gray-50 uppercase"
>
{' '}
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 dark:divide-neutral-700">
{!currentDocuments?.length && (
<tr>
<td
colSpan={4}
className="!py-4 text-gray-800 dark:text-neutral-200 text-center"
>
{t('settings.documents.noData')}
</td>
</tr>
)}
{Array.isArray(currentDocuments) &&
currentDocuments.map((document, index) => (
<tr key={index} className="text-nowrap font-normal">
<td
title={document.name}
className="px-6 py-4 whitespace-nowrap text-left font-medium text-gray-800 dark:text-neutral-200"
>
<img
id={`img-${index}`}
src={Trash}
alt="Delete"
className="h-full w-full cursor-pointer"
/>
</button>
)}
{document.syncFrequency && (
<div className="ml-2">
<DropdownMenu
name="Sync"
options={syncOptions}
onSelect={(value: string) => {
handleManageSync(document, value);
}}
defaultValue={document.syncFrequency}
icon={SyncIcon}
/>
</div>
)}
</div>
</td>
</tr>
))}
</tbody>
</table>
{truncate(document.name, 50)}
</td>
<td className="px-6 py-4 whitespace-nowrap text-center font-medium text-gray-800 dark:text-neutral-200">
{document.date}
</td>
<td className="px-6 py-4 whitespace-nowrap text-center font-medium text-gray-800 dark:text-neutral-200">
{document.tokens
? formatTokens(+document.tokens)
: ''}
</td>
{/*}
<td className="px-6 py-4 whitespace-nowrap text-center text-sm font-medium text-gray-800 dark:text-neutral-200">
{document.type === 'remote'
? 'Pre-loaded'
: 'Private'}
</td>
*/}
<td className="px-6 py-4 whitespace-nowrap text-left text-sm font-medium flex">
<div className="min-w-[150px] flex flex-row items-center ml-auto gap-10">
{document.type !== 'remote' && (
<img
src={Trash}
alt="Delete"
className="h-4 w-4 cursor-pointer opacity-60 hover:opacity-100"
id={`img-${index}`}
onClick={(event) => {
event.stopPropagation();
handleDeleteDocument(index, document);
}}
/>
)}
{document.syncFrequency && (
<div className="ml-2">
<DropdownMenu
name="Sync"
options={syncOptions}
onSelect={(value: string) => {
handleManageSync(document, value);
}}
defaultValue={document.syncFrequency}
icon={SyncIcon}
/>
</div>
)}
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
)}
</div>
{/* outside scrollable area */}
<Pagination
currentPage={currentPage}
totalPages={totalPages}
rowsPerPage={rowsPerPage}
onPageChange={(page) => {
setCurrentPage(page);
refreshDocs(undefined, page, rowsPerPage);
}}
onRowsPerPageChange={(rows) => {
setRowsPerPage(rows);
setCurrentPage(1);
refreshDocs(undefined, 1, rows);
}}
/>
{/* Conditionally render the Upload modal based on modalState */}
{modalState === 'ACTIVE' && (
<div className="fixed top-0 left-0 w-screen h-screen z-50 flex items-center justify-center bg-transparent">
<div className="w-full h-full bg-transparent flex flex-col items-center justify-center p-8">
{/* Your Upload component */}
<Upload
modalState={modalState}
receivedFile={[]}
setModalState={setModalState}
isOnboarding={isOnboarding}
renderTab={null}
close={() => setModalState('INACTIVE')}
/>
</div>
</div>
)}
</div>
{/* Pagination component with props:
# Note: Every time the page changes,
the refreshDocs function is called with the updated page number and rows per page.
and reset cursor paginated query parameter to undefined.
*/}
<Pagination
currentPage={currentPage}
totalPages={totalPages}
rowsPerPage={rowsPerPage}
onPageChange={(page) => {
setCurrentPage(page);
refreshDocs(sortField, page, rowsPerPage);
}}
onRowsPerPageChange={(rows) => {
setRowsPerPage(rows);
setCurrentPage(1);
refreshDocs(sortField, 1, rows);
}}
/>
</div>
);
};

View File

@@ -40,6 +40,14 @@ export default function General() {
label: 'Mandarin',
value: 'zh',
},
{
label: 'Traditional Chinese',
value: 'zhTW',
},
{
label: 'Russian',
value: 'ru',
},
];
const chunks = ['0', '2', '4', '6', '8', '10'];
const token_limits = new Map([

View File

@@ -4,7 +4,6 @@ import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import userService from '../api/services/userService';
import Exit from '../assets/exit.svg';
import ArrowLeft from '../assets/arrow-left.svg';
import FileUpload from '../assets/file_upload.svg';
import WebsiteCollect from '../assets/website_collect.svg';
@@ -17,17 +16,22 @@ import {
setSourceDocs,
selectSourceDocs,
} from '../preferences/preferenceSlice';
import WrapperModal from '../modals/WrapperModal';
function Upload({
modalState,
receivedFile = [],
setModalState,
isOnboarding,
renderTab = null,
close,
}: {
modalState: ActiveState;
receivedFile: File[];
setModalState: (state: ActiveState) => void;
isOnboarding: boolean;
renderTab: string | null;
close: () => void;
}) {
const [docName, setDocName] = useState('');
const [docName, setDocName] = useState(receivedFile[0]?.name);
const [urlName, setUrlName] = useState('');
const [url, setUrl] = useState('');
const [repoUrl, setRepoUrl] = useState(''); // P3f93
@@ -38,8 +42,8 @@ function Upload({
search_queries: [''],
number_posts: 10,
});
const [activeTab, setActiveTab] = useState<string | null>(null);
const [files, setfiles] = useState<File[]>([]);
const [activeTab, setActiveTab] = useState<string | null>(renderTab);
const [files, setfiles] = useState<File[]>(receivedFile);
const [progress, setProgress] = useState<{
type: 'UPLOAD' | 'TRAINING';
percentage: number;
@@ -332,6 +336,9 @@ function Upload({
],
'application/vnd.openxmlformats-officedocument.presentationml.presentation':
['.pptx'],
'image/png': ['.png'],
'image/jpeg': ['.jpeg'],
'image/jpg': ['.jpg'],
},
});
@@ -600,7 +607,30 @@ function Upload({
) : (
<button
onClick={uploadRemote}
className={`ml-2 cursor-pointer rounded-3xl bg-purple-30 py-2 px-6 text-sm text-white hover:bg-[#6F3FD1]`}
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>
@@ -626,28 +656,18 @@ function Upload({
}
return (
<article
className={`${
modalState === 'ACTIVE' ? 'visible' : 'hidden'
} absolute z-30 bg-gray-alpha flex items-center justify-center h-[calc(100vh-4rem)] md:h-screen w-full`}
<WrapperModal
isPerformingTask={progress !== undefined && progress.percentage < 100}
close={() => {
close();
setDocName('');
setfiles([]);
setModalState('INACTIVE');
setActiveTab(null);
}}
>
<article className="relative mx-auto flex w-[90vw] max-w-lg flex-col gap-4 rounded-lg bg-white p-6 shadow-lg dark:bg-outer-space h-fit-content">
{!isOnboarding && !progress && (
<button
className="absolute top-4 right-4 m-1 w-3"
onClick={() => {
setDocName('');
setfiles([]);
setModalState('INACTIVE');
setActiveTab(null);
}}
>
<img className="filter dark:invert" src={Exit} />
</button>
)}
{view}
</article>
</article>
{view}
</WrapperModal>
);
}

View File

@@ -0,0 +1,4 @@
export function truncate(str: string, n: number) {
// slices long strings and ends with ...
return str.length > n ? str.slice(0, n - 1) + '...' : str;
}

View File

@@ -1,55 +0,0 @@
# LLM Document Analysis by [LexEU](https://www.lexeu.ai/) Competition
## 🏆 Competition Details:
Welcome to the LLM Document Analysis by [LexEU](https://www.lexeu.ai/) competition, part of Hacktoberfest! This challenge is designed for participants who can devise the best new retrieval or workflow method to analyze a document using EU laws.
### 🏅 Prizes:
- **1st Place:** $200 + Special Holopin
- **2nd Place:** $100 + Special Holopin
- **3rd Place:** $50 + Special Holopin
- **Top 3 Winners:** Special Holopin
### 📆 Timeline:
- **Competition Announcement:** 1st October
- **Deadline for Submissions:** 8th November
- **Results Announcement:** Early November
## 📜 How to Participate:
Participants are required to analyze a given test contract by scraping EU law data, storing it in a database, and retrieving only the relevant portions for analysis. The solution must be optimized for efficiency, using a maximum of 500k tokens.
### Steps to Participate:
1. **Download Test Contract:** You can download it via this [link](https://docs.google.com/document/d/198d7gFJbVWttkIS9ZRUs_PTKIjhsOUeR/edit?usp=sharing&ouid=107667025862106683614&rtpof=true&sd=true).
2. **Ingest EU Law Data:** Gather and store data in any format, it's available [here](https://eur-lex.europa.eu/browse/directories/legislation.html?displayProfile=lastConsDocProfile&classification=in-force).
3. **Optimized Data Retrieval:** Implement methods to retrieve only small, relevant portions of the law data for efficient analysis of the test contract. Try to create a custom retriever and a parser.
4. **Analyze the Contract:** Use your optimized retrieval method to analyze the test contract against the EU law data.
5. **Submission Criteria:** Your solution will be judged based on:
- Amount of corrections/inconsistencies found
- Number of tokens used (Maximum 500k tokens)
- Your submission should be a fork of DocsGPT where all the ingestion and analysis steps can be replicated
### Submission Instructions:
1. **Submit Your Work:** Once you finish your analysis, submit your solution by filling out this [form](https://airtable.com/appikMaJwdHhC1SDP/pagLWdew2HKpEaBKr/form).
2. **Private Test Contract:** Your solution will also be benchmarked against a private test contract to validate its efficiency and effectiveness.
3. **Evaluation:** The winners will be evaluated based on the effectiveness of their solution in identifying corrections/inconsistencies and the number of tokens used in the process.
### Resources:
- **Documentation:** Refer to our [Documentation](https://docs.docsgpt.cloud/) for guidance.
- **Discord Support:** Join our [Discord](https://discord.gg/n5BX8dh8rU) server for support and discussions related to the competition.
- Try looking at existing [retrievers](https://github.com/arc53/DocsGPT/tree/main/application/retriever) and maybe creating a custom one
- Try looking at [worker.py](https://github.com/arc53/DocsGPT/blob/main/application/worker.py) which ingests data and creating a custom one for ingesting EU law
## 👥 Community and Support:
If you need assistance, feel free to join our [Discord](https://discord.gg/n5BX8dh8rU) server. We're here to help newcomers, so don't hesitate to jump in and ask questions!
## 📢 Announcement:
Stay tuned for updates, and good luck to all participants!
Thank you for participating in the LLM Document Analysis by LexEU competition. Your innovative solutions could not only win you prizes but also contribute significantly to the DocsGPT community. Happy coding! 🚀
---

View File

@@ -1,5 +0,0 @@
# Elastic Beanstalk Files
.elasticbeanstalk/*
!.elasticbeanstalk/*.cfg.yml
!.elasticbeanstalk/*.global.yml

View File

@@ -1,11 +0,0 @@
FROM node:20.6.1-bullseye-slim
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8080
CMD [ "npm", "run", "start"]

File diff suppressed because it is too large Load Diff

View File

@@ -1,22 +0,0 @@
{
"name": "mock-backend",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node src/server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"json-server": "^0.17.4",
"uuid": "^9.0.1"
},
"devDependencies": {
"@types/json-server": "^0.14.5"
}
}

View File

@@ -1,244 +0,0 @@
{
"combine": [
{
"date": "default",
"description": "default",
"docLink": "default",
"fullName": "default",
"language": "default",
"location": "local",
"model": "openai_text-embedding-ada-002",
"name": "default",
"version": ""
},
{
"date": "13/02/2023",
"description": "Serverless Framework, the serverless application framework for building web, mobile and IoT applications on AWS Lambda, Azure Functions, Google CloudFunctions & more!",
"docLink": "https://serverless.com/framework/docs/",
"fullName": "Serverless Framework",
"language": "serverless",
"location": "remote",
"name": "serverless framework",
"version": "3.27.0"
},
{
"date": "15/02/2023",
"description": "Machine Learning in Python",
"docLink": "https://scikit-learn.org/stable/",
"fullName": "scikit-learn",
"language": "python",
"location": "remote",
"model": "openai_text-embedding-ada-002",
"name": "scikit-learn",
"version": "1.2.1"
},
{
"date": "07/02/2023",
"description": "Machine Learning in Python",
"docLink": "https://scikit-learn.org/stable/",
"fullName": "scikit-learn",
"language": "python",
"location": "remote",
"name": "scikit-learn",
"version": "1.2.1"
},
{
"date": "07/02/2023",
"description": "Pandas is alibrary providing high-performance, easy-to-use data structures and data analysis tools for the Python programming language.",
"docLink": "https://pandas.pydata.org/docs/",
"fullName": "Pandas",
"language": "python",
"location": "remote",
"model": "openai_text-embedding-ada-002",
"name": "pandas",
"version": "1.5.3"
},
{
"date": "07/02/2023",
"description": "Pandas is alibrary providing high-performance, easy-to-use data structures and data analysis tools for the Python programming language.",
"docLink": "https://pandas.pydata.org/docs/",
"fullName": "Pandas",
"language": "python",
"location": "remote",
"name": "pandas",
"version": "1.5.3"
},
{
"date": "29/02/2023",
"description": "Python is a programming language that lets you work quickly and integrate systems more effectively.",
"docLink": "https://docs.python.org/3/",
"fullName": "Python",
"language": "python",
"location": "remote",
"model": "huggingface_sentence-transformers-all-mpnet-base-v2",
"name": "python",
"version": "3.11.1"
},
{
"date": "15/02/2023",
"description": "Python is a programming language that lets you work quickly and integrate systems more effectively.",
"docLink": "https://docs.python.org/3/",
"fullName": "Python",
"language": "python",
"location": "remote",
"model": "openai_text-embedding-ada-002",
"name": "python",
"version": "3.11.1"
},
{
"date": "07/02/2023",
"description": "Python is a programming language that lets you work quickly and integrate systems more effectively.",
"docLink": "https://docs.python.org/3/",
"fullName": "Python",
"language": "python",
"location": "remote",
"name": "python",
"version": "3.11.1"
},
{
"date": "08/02/2023",
"description": "GPT Index is a project consisting of a set of data structures designed to make it easier to use large external knowledge bases with LLMs.",
"docLink": "https://gpt-index.readthedocs.io/en/latest/index.html",
"fullName": "LangChain",
"language": "python",
"location": "remote",
"name": "gpt-index",
"version": "0.4.0"
},
{
"date": "15/02/2023",
"description": "Large language models (LLMs) are emerging as a transformative technology, enabling developers to build applications that they previously could not.",
"docLink": "https://langchain.readthedocs.io/en/latest/index.html",
"fullName": "LangChain",
"language": "python",
"location": "remote",
"model": "openai_text-embedding-ada-002",
"name": "langchain",
"version": "0.0.87"
},
{
"date": "07/02/2023",
"description": "Large language models (LLMs) are emerging as a transformative technology, enabling developers to build applications that they previously could not.",
"docLink": "https://langchain.readthedocs.io/en/latest/index.html",
"fullName": "LangChain",
"language": "python",
"location": "remote",
"name": "langchain",
"version": "0.0.79"
},
{
"date": "13/03/2023",
"description": "Large language models (LLMs) are emerging as a transformative technology, enabling developers to build applications that they previously could not.",
"docLink": "https://langchain.readthedocs.io/en/latest/index.html",
"fullName": "LangChain",
"language": "python",
"location": "remote",
"model": "openai_text-embedding-ada-002",
"name": "langchain",
"version": "0.0.109"
},
{
"date": "16/03/2023",
"description": "A JavaScript library for building user interfaces\nGet Started\n",
"docLink": "https://reactjs.org/",
"fullName": "React",
"language": "javascript",
"location": "remote",
"model": "openai_text-embedding-ada-002",
"name": "react",
"version": "v18.2.0"
},
{
"date": "15/02/2023",
"description": "is a lightweight, interpreted, or just-in-time compiled programming language with first-class functions.",
"docLink": "https://developer.mozilla.org/en-US/docs/Web/JavaScript",
"fullName": "JavaScript",
"language": "javascript",
"location": "remote",
"model": "openai_text-embedding-ada-002",
"name": "javascript",
"version": "ES2015"
},
{
"date": "16/03/2023",
"description": "An approachable, performant and versatile framework for building web user interfaces. ",
"docLink": "https://vuejs.org/",
"fullName": "Vue.js",
"language": "javascript",
"location": "remote",
"model": "openai_text-embedding-ada-002",
"name": "vuejs",
"version": "v3.3.0"
},
{
"date": "16/03/2023",
"description": "Get ready for a development environment that can finally catch up with you.",
"docLink": "https://vitejs.dev/",
"fullName": "Vite",
"language": "javascript",
"location": "remote",
"model": "openai_text-embedding-ada-002",
"name": "vitejs",
"version": "v4.2.0"
},
{
"date": "15/02/2023",
"description": "Solidity is an object-oriented, high-level language for implementing smart contracts.",
"docLink": "https://docs.soliditylang.org/en/v0.8.18/",
"fullName": "Solidity",
"language": "ethereum",
"location": "remote",
"model": "openai_text-embedding-ada-002",
"name": "solidity",
"version": "0.8.18"
},
{
"date": "07/02/2023",
"description": "Solidity is an object-oriented, high-level language for implementing smart contracts.",
"docLink": "https://docs.soliditylang.org/en/v0.8.18/",
"fullName": "Solidity",
"language": "ethereum",
"location": "remote",
"name": "solidity",
"version": "0.8.18"
},
{
"date": "28/02/2023",
"description": "GPT-powered chat for documentation search & assistance. ",
"docLink": "https://github.com/arc53/DocsGPT/wiki",
"fullName": "DocsGPT",
"language": "docsgpt",
"location": "remote",
"model": "huggingface_sentence-transformers-all-mpnet-base-v2",
"name": "docsgpt",
"version": "0.1.0"
},
{
"date": "28/02/2023",
"description": "GPT-powered chat for documentation search & assistance. ",
"docLink": "https://github.com/arc53/DocsGPT/wiki",
"fullName": "DocsGPT",
"language": "docsgpt",
"location": "remote",
"model": "openai_text-embedding-ada-002",
"name": "docsgpt",
"version": "0.1.0"
}
],
"conversations": [
{
"id": "65cf39c936523eea21ebe117",
"name": "Request clarification"
},
{
"id": "65cf39ba36523eea21ebe116",
"name": "Clarification request"
},
{
"id": "65cf37e97d527c332bbac933",
"name": "Greetings, assistance inquiry."
}],
"docs_check": {
"status": "loaded"
}
}

View File

@@ -1,7 +0,0 @@
{
"/api/*": "/$1",
"/get_conversations": "/conversations",
"/get_single_conversation?id=:id": "/conversations/:id",
"/delete_conversation?id=:id": "/conversations/:id",
"/conversations?id=:id": "/conversations/:id"
}

View File

@@ -1,131 +0,0 @@
import jsonServer from "json-server";
import routes from "./mocks/routes.json" assert { type: "json" };
import { v4 as uuid } from "uuid";
import cors from 'cors'
const server = jsonServer.create();
const router = jsonServer.router("./src/mocks/db.json");
const middlewares = jsonServer.defaults();
const localStorage = [];
server.use(middlewares);
server.use(cors({ origin: ['*'] }))
server.use(jsonServer.rewriter(routes));
server.use((req, res, next) => {
if (req.method === "POST") {
if (req.url.includes("/delete_conversation")) {
req.method = "DELETE";
} else if (req.url === "/upload") {
const taskId = uuid();
localStorage.push(taskId);
}
}
next();
});
router.render = (req, res) => {
if (req.url === "/feedback") {
res.status(200).jsonp({ status: "ok" });
} else if (req.url === "/upload") {
res.status(200).jsonp({
status: "ok",
task_id: localStorage[localStorage.length - 1],
});
} else if (req.url.includes("/task_status")) {
const taskId = req.query["task_id"];
const taskIdExists = localStorage.includes(taskId);
if (taskIdExists) {
res.status(200).jsonp({
result: {
directory: "temp",
filename: "install.rst",
formats: [".rst", ".md", ".pdf"],
name_job: "somename",
user: "local",
},
status: "SUCCESS",
});
} else {
res.status(404).jsonp({});
}
} else if (req.url === "/stream" && req.method === "POST") {
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive'
});
const message = ('Hi, How are you today?').split(' ');
let index = 0;
const interval = setInterval(() => {
if (index < message.length) {
res.write(`data: {"answer": "${message[index++]} "}\n`);
} else {
res.write(`data: {"type": "id", "id": "65cbc39d11f077b9eeb06d26"}\n`)
res.write(`data: {"type": "end"}\n`)
clearInterval(interval); // Stop the interval once the message is fully streamed
res.end(); // End the response
}
}, 500); // Send a word every 1 second
}
else if (req.url === '/search' && req.method === 'POST') {
res.status(200).json(
[
{
"text": "\n\n/api/answer\nIt's a POST request that sends a JSON in body with 4 values. It will receive an answer for a user provided question.\n",
"title": "API-docs.md"
},
{
"text": "\n\nOur Standards\n\nExamples of behavior that contribute to a positive environment for our\ncommunity include:\n* Demonstrating empathy and kindness towards other people\n",
"title": "How-to-use-different-LLM.md"
}
]
)
}
else if (req.url === '/get_prompts' && req.method === 'GET') {
res.status(200).json([
{
"id": "default",
"name": "default",
"type": "public"
},
{
"id": "creative",
"name": "creative",
"type": "public"
},
{
"id": "strict",
"name": "strict",
"type": "public"
}
]);
}
else if (req.url.startsWith('/get_single_prompt') && req.method==='GET') {
const id = req.query.id;
console.log('hre');
if (id === 'creative')
res.status(200).json({
"content": "You are a DocsGPT, friendly and helpful AI assistant by Arc53 that provides help with documents. You give thorough answers with code examples if possible."
})
else if (id === 'strict') {
res.status(200).json({
"content": "You are an AI Assistant, DocsGPT, adept at offering document assistance. \nYour expertise lies in providing answer on top of provided context."
})
}
else {
res.status(200).json({
"content": "You are a helpful AI assistant, DocsGPT, specializing in document assistance, designed to offer detailed and informative responses."
})
}
}
else {
res.status(res.statusCode).jsonp(res.locals.data);
}
};
server.use(router);
server.listen(8080, () => {
console.log("JSON Server is running");
});

1716
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +0,0 @@
{
"devDependencies": {
"eslint": "^8.53.0",
"lint-staged": "^15.1.0",
"prettier": "^3.1.0"
}
}