mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-29 16:43:16 +00:00
Compare commits
1 Commits
api-answer
...
dependabot
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e211eb71bd |
2
.gitattributes
vendored
2
.gitattributes
vendored
@@ -1,2 +0,0 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
@@ -52,11 +52,8 @@
|
||||
- [x] Chatbots menu re-design to handle tools, agent types, and more (April 2025)
|
||||
- [x] New input box in the conversation menu (April 2025)
|
||||
- [x] Add triggerable actions / tools (webhook) (April 2025)
|
||||
- [x] Agent optimisations (May 2025)
|
||||
- [ ] Filesystem sources update (July 2025)
|
||||
- [ ] Anthropic Tool compatibility (July 2025)
|
||||
- [ ] MCP support (July 2025)
|
||||
- [ ] Add OAuth 2.0 authentication for tools and sources (August 2025)
|
||||
- [ ] Anthropic Tool compatibility (May 2025)
|
||||
- [ ] Add OAuth 2.0 authentication for tools and sources
|
||||
- [ ] Agent scheduling
|
||||
|
||||
You can find our full roadmap [here](https://github.com/orgs/arc53/projects/2). Please don't hesitate to contribute or create issues, it helps us improve DocsGPT!
|
||||
|
||||
@@ -91,8 +91,8 @@ class BaseAgent(ABC):
|
||||
user_tools_collection = db["user_tools"]
|
||||
user_tools = user_tools_collection.find({"user": user, "status": True})
|
||||
user_tools = list(user_tools)
|
||||
|
||||
return {str(i): tool for i, tool in enumerate(user_tools)}
|
||||
tools_by_id = {str(tool["_id"]): tool for tool in user_tools}
|
||||
return tools_by_id
|
||||
|
||||
def _build_tool_parameters(self, action):
|
||||
params = {"type": "object", "properties": {}, "required": []}
|
||||
@@ -313,6 +313,7 @@ class BaseAgent(ABC):
|
||||
if hasattr(response, "message") and getattr(response.message, "content", None):
|
||||
yield {"answer": response.message.content}
|
||||
return
|
||||
|
||||
processed_response_gen = self._llm_handler(
|
||||
response, tools_dict, messages, log_context, self.attachments
|
||||
)
|
||||
|
||||
@@ -8,7 +8,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ClassicAgent(BaseAgent):
|
||||
"""A simplified agent with clear execution flow.
|
||||
"""A simplified classic agent with clear execution flow.
|
||||
|
||||
Usage:
|
||||
1. Processes a query through retrieval
|
||||
|
||||
@@ -25,35 +25,27 @@ class BraveSearchTool(Tool):
|
||||
else:
|
||||
raise ValueError(f"Unknown action: {action_name}")
|
||||
|
||||
def _web_search(
|
||||
self,
|
||||
query,
|
||||
country="ALL",
|
||||
search_lang="en",
|
||||
count=10,
|
||||
offset=0,
|
||||
safesearch="off",
|
||||
freshness=None,
|
||||
result_filter=None,
|
||||
extra_snippets=False,
|
||||
summary=False,
|
||||
):
|
||||
def _web_search(self, query, country="ALL", search_lang="en", count=10,
|
||||
offset=0, safesearch="off", freshness=None,
|
||||
result_filter=None, extra_snippets=False, summary=False):
|
||||
"""
|
||||
Performs a web search using the Brave Search API.
|
||||
"""
|
||||
print(f"Performing Brave web search for: {query}")
|
||||
|
||||
|
||||
url = f"{self.base_url}/web/search"
|
||||
|
||||
|
||||
# Build query parameters
|
||||
params = {
|
||||
"q": query,
|
||||
"country": country,
|
||||
"search_lang": search_lang,
|
||||
"count": min(count, 20),
|
||||
"offset": min(offset, 9),
|
||||
"safesearch": safesearch,
|
||||
"safesearch": safesearch
|
||||
}
|
||||
|
||||
|
||||
# Add optional parameters only if they have values
|
||||
if freshness:
|
||||
params["freshness"] = freshness
|
||||
if result_filter:
|
||||
@@ -62,69 +54,68 @@ class BraveSearchTool(Tool):
|
||||
params["extra_snippets"] = 1
|
||||
if summary:
|
||||
params["summary"] = 1
|
||||
|
||||
# Set up headers
|
||||
headers = {
|
||||
"Accept": "application/json",
|
||||
"Accept-Encoding": "gzip",
|
||||
"X-Subscription-Token": self.token,
|
||||
"X-Subscription-Token": self.token
|
||||
}
|
||||
|
||||
|
||||
# Make the request
|
||||
response = requests.get(url, params=params, headers=headers)
|
||||
|
||||
|
||||
if response.status_code == 200:
|
||||
return {
|
||||
"status_code": response.status_code,
|
||||
"results": response.json(),
|
||||
"message": "Search completed successfully.",
|
||||
"message": "Search completed successfully."
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"status_code": response.status_code,
|
||||
"message": f"Search failed with status code: {response.status_code}.",
|
||||
"message": f"Search failed with status code: {response.status_code}."
|
||||
}
|
||||
|
||||
def _image_search(
|
||||
self,
|
||||
query,
|
||||
country="ALL",
|
||||
search_lang="en",
|
||||
count=5,
|
||||
safesearch="off",
|
||||
spellcheck=False,
|
||||
):
|
||||
|
||||
def _image_search(self, query, country="ALL", search_lang="en", count=5,
|
||||
safesearch="off", spellcheck=False):
|
||||
"""
|
||||
Performs an image search using the Brave Search API.
|
||||
"""
|
||||
print(f"Performing Brave image search for: {query}")
|
||||
|
||||
|
||||
url = f"{self.base_url}/images/search"
|
||||
|
||||
|
||||
# Build query parameters
|
||||
params = {
|
||||
"q": query,
|
||||
"country": country,
|
||||
"search_lang": search_lang,
|
||||
"count": min(count, 100), # API max is 100
|
||||
"safesearch": safesearch,
|
||||
"spellcheck": 1 if spellcheck else 0,
|
||||
"spellcheck": 1 if spellcheck else 0
|
||||
}
|
||||
|
||||
|
||||
# Set up headers
|
||||
headers = {
|
||||
"Accept": "application/json",
|
||||
"Accept-Encoding": "gzip",
|
||||
"X-Subscription-Token": self.token,
|
||||
"X-Subscription-Token": self.token
|
||||
}
|
||||
|
||||
|
||||
# Make the request
|
||||
response = requests.get(url, params=params, headers=headers)
|
||||
|
||||
|
||||
if response.status_code == 200:
|
||||
return {
|
||||
"status_code": response.status_code,
|
||||
"results": response.json(),
|
||||
"message": "Image search completed successfully.",
|
||||
"message": "Image search completed successfully."
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"status_code": response.status_code,
|
||||
"message": f"Image search failed with status code: {response.status_code}.",
|
||||
"message": f"Image search failed with status code: {response.status_code}."
|
||||
}
|
||||
|
||||
def get_actions_metadata(self):
|
||||
@@ -139,14 +130,42 @@ class BraveSearchTool(Tool):
|
||||
"type": "string",
|
||||
"description": "The search query (max 400 characters, 50 words)",
|
||||
},
|
||||
# "country": {
|
||||
# "type": "string",
|
||||
# "description": "The 2-character country code (default: US)",
|
||||
# },
|
||||
"search_lang": {
|
||||
"type": "string",
|
||||
"description": "The search language preference (default: en)",
|
||||
},
|
||||
# "count": {
|
||||
# "type": "integer",
|
||||
# "description": "Number of results to return (max 20, default: 10)",
|
||||
# },
|
||||
# "offset": {
|
||||
# "type": "integer",
|
||||
# "description": "Pagination offset (max 9, default: 0)",
|
||||
# },
|
||||
# "safesearch": {
|
||||
# "type": "string",
|
||||
# "description": "Filter level for adult content (off, moderate, strict)",
|
||||
# },
|
||||
"freshness": {
|
||||
"type": "string",
|
||||
"description": "Time filter for results (pd: last 24h, pw: last week, pm: last month, py: last year)",
|
||||
},
|
||||
# "result_filter": {
|
||||
# "type": "string",
|
||||
# "description": "Comma-delimited list of result types to include",
|
||||
# },
|
||||
# "extra_snippets": {
|
||||
# "type": "boolean",
|
||||
# "description": "Get additional excerpts from result pages",
|
||||
# },
|
||||
# "summary": {
|
||||
# "type": "boolean",
|
||||
# "description": "Enable summary generation in search results",
|
||||
# }
|
||||
},
|
||||
"required": ["query"],
|
||||
"additionalProperties": False,
|
||||
@@ -162,21 +181,37 @@ class BraveSearchTool(Tool):
|
||||
"type": "string",
|
||||
"description": "The search query (max 400 characters, 50 words)",
|
||||
},
|
||||
# "country": {
|
||||
# "type": "string",
|
||||
# "description": "The 2-character country code (default: US)",
|
||||
# },
|
||||
# "search_lang": {
|
||||
# "type": "string",
|
||||
# "description": "The search language preference (default: en)",
|
||||
# },
|
||||
"count": {
|
||||
"type": "integer",
|
||||
"description": "Number of results to return (max 100, default: 5)",
|
||||
},
|
||||
# "safesearch": {
|
||||
# "type": "string",
|
||||
# "description": "Filter level for adult content (off, strict). Default: strict",
|
||||
# },
|
||||
# "spellcheck": {
|
||||
# "type": "boolean",
|
||||
# "description": "Whether to spellcheck provided query (default: true)",
|
||||
# }
|
||||
},
|
||||
"required": ["query"],
|
||||
"additionalProperties": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
def get_config_requirements(self):
|
||||
return {
|
||||
"token": {
|
||||
"type": "string",
|
||||
"description": "Brave Search API key for authentication",
|
||||
"type": "string",
|
||||
"description": "Brave Search API key for authentication"
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
from application.agents.tools.base import Tool
|
||||
from duckduckgo_search import DDGS
|
||||
|
||||
|
||||
class DuckDuckGoSearchTool(Tool):
|
||||
"""
|
||||
DuckDuckGo Search
|
||||
A tool for performing web and image searches using DuckDuckGo.
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
|
||||
def execute_action(self, action_name, **kwargs):
|
||||
actions = {
|
||||
"ddg_web_search": self._web_search,
|
||||
"ddg_image_search": self._image_search,
|
||||
}
|
||||
|
||||
if action_name in actions:
|
||||
return actions[action_name](**kwargs)
|
||||
else:
|
||||
raise ValueError(f"Unknown action: {action_name}")
|
||||
|
||||
def _web_search(
|
||||
self,
|
||||
query,
|
||||
max_results=5,
|
||||
):
|
||||
print(f"Performing DuckDuckGo web search for: {query}")
|
||||
|
||||
try:
|
||||
results = DDGS().text(
|
||||
query,
|
||||
max_results=max_results,
|
||||
)
|
||||
|
||||
return {
|
||||
"status_code": 200,
|
||||
"results": results,
|
||||
"message": "Web search completed successfully.",
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"status_code": 500,
|
||||
"message": f"Web search failed: {str(e)}",
|
||||
}
|
||||
|
||||
def _image_search(
|
||||
self,
|
||||
query,
|
||||
max_results=5,
|
||||
):
|
||||
print(f"Performing DuckDuckGo image search for: {query}")
|
||||
|
||||
try:
|
||||
results = DDGS().images(
|
||||
keywords=query,
|
||||
max_results=max_results,
|
||||
)
|
||||
|
||||
return {
|
||||
"status_code": 200,
|
||||
"results": results,
|
||||
"message": "Image search completed successfully.",
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"status_code": 500,
|
||||
"message": f"Image search failed: {str(e)}",
|
||||
}
|
||||
|
||||
def get_actions_metadata(self):
|
||||
return [
|
||||
{
|
||||
"name": "ddg_web_search",
|
||||
"description": "Perform a web search using DuckDuckGo.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
},
|
||||
"max_results": {
|
||||
"type": "integer",
|
||||
"description": "Number of results to return (default: 5)",
|
||||
},
|
||||
},
|
||||
"required": ["query"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "ddg_image_search",
|
||||
"description": "Perform an image search using DuckDuckGo.",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
},
|
||||
"max_results": {
|
||||
"type": "integer",
|
||||
"description": "Number of results to return (default: 5, max: 50)",
|
||||
},
|
||||
},
|
||||
"required": ["query"],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
def get_config_requirements(self):
|
||||
return {}
|
||||
@@ -599,9 +599,6 @@ class Answer(Resource):
|
||||
"isNoneDoc": fields.Boolean(
|
||||
required=False, description="Flag indicating if no document is used"
|
||||
),
|
||||
"attachments": fields.List(
|
||||
fields.String, required=False, description="List of attachment IDs"
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
@@ -617,11 +614,10 @@ class Answer(Resource):
|
||||
try:
|
||||
question = data["question"]
|
||||
history = limit_chat_history(
|
||||
json.loads(data.get("history", "[]")), gpt_model=gpt_model
|
||||
json.loads(data.get("history", [])), gpt_model=gpt_model
|
||||
)
|
||||
conversation_id = data.get("conversation_id")
|
||||
prompt_id = data.get("prompt_id", "default")
|
||||
attachment_ids = data.get("attachments", [])
|
||||
chunks = int(data.get("chunks", 2))
|
||||
token_limit = data.get("token_limit", settings.DEFAULT_MAX_HISTORY)
|
||||
retriever_name = data.get("retriever", "classic")
|
||||
@@ -651,14 +647,10 @@ class Answer(Resource):
|
||||
if not decoded_token:
|
||||
return make_response({"error": "Unauthorized"}, 401)
|
||||
|
||||
attachments = get_attachments_content(
|
||||
attachment_ids, decoded_token.get("sub")
|
||||
)
|
||||
|
||||
prompt = get_prompt(prompt_id)
|
||||
|
||||
logger.info(
|
||||
f"/api/answer - request_data: {data}, source: {source}, attachments: {len(attachments)}",
|
||||
f"/api/answer - request_data: {data}, source: {source}",
|
||||
extra={"data": json.dumps({"request_data": data, "source": source})},
|
||||
)
|
||||
|
||||
@@ -672,7 +664,6 @@ class Answer(Resource):
|
||||
prompt=prompt,
|
||||
chat_history=history,
|
||||
decoded_token=decoded_token,
|
||||
attachments=attachments,
|
||||
)
|
||||
|
||||
retriever = RetrieverCreator.create_retriever(
|
||||
@@ -703,7 +694,6 @@ class Answer(Resource):
|
||||
isNoneDoc=data.get("isNoneDoc"),
|
||||
index=None,
|
||||
should_save_conversation=False,
|
||||
attachment_ids=attachment_ids,
|
||||
):
|
||||
try:
|
||||
event_data = line.replace("data: ", "").strip()
|
||||
@@ -754,7 +744,6 @@ class Answer(Resource):
|
||||
llm,
|
||||
decoded_token,
|
||||
api_key=user_api_key,
|
||||
attachment_ids=attachment_ids,
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ def handle_image_upload(
|
||||
filename = secure_filename(file.filename)
|
||||
upload_path = f"{settings.UPLOAD_FOLDER.rstrip('/')}/{user}/{base_path.rstrip('/')}/{uuid.uuid4()}_{filename}"
|
||||
try:
|
||||
storage.save_file(file, upload_path, storage_class="STANDARD")
|
||||
storage.save_file(file, upload_path)
|
||||
image_url = upload_path
|
||||
except Exception as e:
|
||||
current_app.logger.error(f"Error uploading image: {e}")
|
||||
@@ -879,6 +879,29 @@ class CombinedJson(Resource):
|
||||
"syncFrequency": index.get("sync_frequency", ""),
|
||||
}
|
||||
)
|
||||
if "duckduck_search" in settings.RETRIEVERS_ENABLED:
|
||||
data.append(
|
||||
{
|
||||
"name": "DuckDuckGo Search",
|
||||
"date": "duckduck_search",
|
||||
"model": settings.EMBEDDINGS_NAME,
|
||||
"location": "custom",
|
||||
"tokens": "",
|
||||
"retriever": "duckduck_search",
|
||||
}
|
||||
)
|
||||
if "brave_search" in settings.RETRIEVERS_ENABLED:
|
||||
data.append(
|
||||
{
|
||||
"name": "Brave Search",
|
||||
"language": "en",
|
||||
"date": "brave_search",
|
||||
"model": settings.EMBEDDINGS_NAME,
|
||||
"location": "custom",
|
||||
"tokens": "",
|
||||
"retriever": "brave_search",
|
||||
}
|
||||
)
|
||||
except Exception as err:
|
||||
current_app.logger.error(f"Error retrieving sources: {err}", exc_info=True)
|
||||
return make_response(jsonify({"success": False}), 400)
|
||||
|
||||
@@ -33,7 +33,7 @@ class Settings(BaseSettings):
|
||||
VECTOR_STORE: str = (
|
||||
"faiss" # "faiss" or "elasticsearch" or "qdrant" or "milvus" or "lancedb"
|
||||
)
|
||||
RETRIEVERS_ENABLED: list = ["classic_rag"]
|
||||
RETRIEVERS_ENABLED: list = ["classic_rag", "duckduck_search"] # also brave_search
|
||||
AGENT_NAME: str = "classic"
|
||||
FALLBACK_LLM_PROVIDER: Optional[str] = None # provider for fallback llm
|
||||
FALLBACK_LLM_NAME: Optional[str] = None # model name for fallback llm
|
||||
@@ -99,6 +99,7 @@ class Settings(BaseSettings):
|
||||
LANCEDB_TABLE_NAME: Optional[str] = (
|
||||
"docsgpts" # Name of the table to use for storing vectors
|
||||
)
|
||||
BRAVE_SEARCH_API_KEY: Optional[str] = None
|
||||
|
||||
FLASK_DEBUG_MODE: bool = False
|
||||
STORAGE_TYPE: str = "local" # local or s3
|
||||
|
||||
@@ -14,5 +14,5 @@ class LLMHandlerCreator:
|
||||
def create_handler(cls, llm_type: str, *args, **kwargs) -> LLMHandler:
|
||||
handler_class = cls.handlers.get(llm_type.lower())
|
||||
if not handler_class:
|
||||
handler_class = OpenAILLMHandler
|
||||
raise ValueError(f"No LLM handler class found for type {llm_type}")
|
||||
return handler_class(*args, **kwargs)
|
||||
|
||||
@@ -75,7 +75,7 @@ transformers==4.51.3
|
||||
typing-extensions==4.12.2
|
||||
typing-inspect==0.9.0
|
||||
tzdata==2024.2
|
||||
urllib3==2.3.0
|
||||
urllib3==2.5.0
|
||||
vine==5.1.0
|
||||
wcwidth==0.2.13
|
||||
werkzeug==3.1.3
|
||||
|
||||
112
application/retriever/brave_search.py
Normal file
112
application/retriever/brave_search.py
Normal file
@@ -0,0 +1,112 @@
|
||||
import json
|
||||
|
||||
from langchain_community.tools import BraveSearch
|
||||
|
||||
from application.core.settings import settings
|
||||
from application.llm.llm_creator import LLMCreator
|
||||
from application.retriever.base import BaseRetriever
|
||||
|
||||
|
||||
class BraveRetSearch(BaseRetriever):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
source,
|
||||
chat_history,
|
||||
prompt,
|
||||
chunks=2,
|
||||
token_limit=150,
|
||||
gpt_model="docsgpt",
|
||||
user_api_key=None,
|
||||
decoded_token=None,
|
||||
):
|
||||
self.question = ""
|
||||
self.source = source
|
||||
self.chat_history = chat_history
|
||||
self.prompt = prompt
|
||||
self.chunks = chunks
|
||||
self.gpt_model = gpt_model
|
||||
self.token_limit = (
|
||||
token_limit
|
||||
if token_limit
|
||||
< settings.LLM_TOKEN_LIMITS.get(
|
||||
self.gpt_model, settings.DEFAULT_MAX_HISTORY
|
||||
)
|
||||
else settings.LLM_TOKEN_LIMITS.get(
|
||||
self.gpt_model, settings.DEFAULT_MAX_HISTORY
|
||||
)
|
||||
)
|
||||
self.user_api_key = user_api_key
|
||||
self.decoded_token = decoded_token
|
||||
|
||||
def _get_data(self):
|
||||
if self.chunks == 0:
|
||||
docs = []
|
||||
else:
|
||||
search = BraveSearch.from_api_key(
|
||||
api_key=settings.BRAVE_SEARCH_API_KEY,
|
||||
search_kwargs={"count": int(self.chunks)},
|
||||
)
|
||||
results = search.run(self.question)
|
||||
results = json.loads(results)
|
||||
|
||||
docs = []
|
||||
for i in results:
|
||||
try:
|
||||
title = i["title"]
|
||||
link = i["link"]
|
||||
snippet = i["snippet"]
|
||||
docs.append({"text": snippet, "title": title, "link": link})
|
||||
except IndexError:
|
||||
pass
|
||||
if settings.LLM_PROVIDER == "llama.cpp":
|
||||
docs = [docs[0]]
|
||||
|
||||
return docs
|
||||
|
||||
def gen(self):
|
||||
docs = self._get_data()
|
||||
|
||||
# join all page_content together with a newline
|
||||
docs_together = "\n".join([doc["text"] for doc in docs])
|
||||
p_chat_combine = self.prompt.replace("{summaries}", docs_together)
|
||||
messages_combine = [{"role": "system", "content": p_chat_combine}]
|
||||
for doc in docs:
|
||||
yield {"source": doc}
|
||||
|
||||
if len(self.chat_history) > 0:
|
||||
for i in self.chat_history:
|
||||
if "prompt" in i and "response" in i:
|
||||
messages_combine.append({"role": "user", "content": i["prompt"]})
|
||||
messages_combine.append(
|
||||
{"role": "assistant", "content": i["response"]}
|
||||
)
|
||||
messages_combine.append({"role": "user", "content": self.question})
|
||||
|
||||
llm = LLMCreator.create_llm(
|
||||
settings.LLM_PROVIDER,
|
||||
api_key=settings.API_KEY,
|
||||
user_api_key=self.user_api_key,
|
||||
decoded_token=self.decoded_token,
|
||||
)
|
||||
|
||||
completion = llm.gen_stream(model=self.gpt_model, messages=messages_combine)
|
||||
for line in completion:
|
||||
yield {"answer": str(line)}
|
||||
|
||||
def search(self, query: str = ""):
|
||||
if query:
|
||||
self.question = query
|
||||
return self._get_data()
|
||||
|
||||
def get_params(self):
|
||||
return {
|
||||
"question": self.question,
|
||||
"source": self.source,
|
||||
"chat_history": self.chat_history,
|
||||
"prompt": self.prompt,
|
||||
"chunks": self.chunks,
|
||||
"token_limit": self.token_limit,
|
||||
"gpt_model": self.gpt_model,
|
||||
"user_api_key": self.user_api_key,
|
||||
}
|
||||
111
application/retriever/duckduck_search.py
Normal file
111
application/retriever/duckduck_search.py
Normal file
@@ -0,0 +1,111 @@
|
||||
from langchain_community.tools import DuckDuckGoSearchResults
|
||||
from langchain_community.utilities import DuckDuckGoSearchAPIWrapper
|
||||
|
||||
from application.core.settings import settings
|
||||
from application.llm.llm_creator import LLMCreator
|
||||
from application.retriever.base import BaseRetriever
|
||||
|
||||
|
||||
class DuckDuckSearch(BaseRetriever):
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
source,
|
||||
chat_history,
|
||||
prompt,
|
||||
chunks=2,
|
||||
token_limit=150,
|
||||
gpt_model="docsgpt",
|
||||
user_api_key=None,
|
||||
decoded_token=None,
|
||||
):
|
||||
self.question = ""
|
||||
self.source = source
|
||||
self.chat_history = chat_history
|
||||
self.prompt = prompt
|
||||
self.chunks = chunks
|
||||
self.gpt_model = gpt_model
|
||||
self.token_limit = (
|
||||
token_limit
|
||||
if token_limit
|
||||
< settings.LLM_TOKEN_LIMITS.get(
|
||||
self.gpt_model, settings.DEFAULT_MAX_HISTORY
|
||||
)
|
||||
else settings.LLM_TOKEN_LIMITS.get(
|
||||
self.gpt_model, settings.DEFAULT_MAX_HISTORY
|
||||
)
|
||||
)
|
||||
self.user_api_key = user_api_key
|
||||
self.decoded_token = decoded_token
|
||||
|
||||
def _get_data(self):
|
||||
if self.chunks == 0:
|
||||
docs = []
|
||||
else:
|
||||
wrapper = DuckDuckGoSearchAPIWrapper(max_results=self.chunks)
|
||||
search = DuckDuckGoSearchResults(api_wrapper=wrapper, output_format="list")
|
||||
results = search.run(self.question)
|
||||
|
||||
docs = []
|
||||
for i in results:
|
||||
try:
|
||||
docs.append(
|
||||
{
|
||||
"text": i.get("snippet", "").strip(),
|
||||
"title": i.get("title", "").strip(),
|
||||
"link": i.get("link", "").strip(),
|
||||
}
|
||||
)
|
||||
except IndexError:
|
||||
pass
|
||||
if settings.LLM_PROVIDER == "llama.cpp":
|
||||
docs = [docs[0]]
|
||||
|
||||
return docs
|
||||
|
||||
def gen(self):
|
||||
docs = self._get_data()
|
||||
|
||||
# join all page_content together with a newline
|
||||
docs_together = "\n".join([doc["text"] for doc in docs])
|
||||
p_chat_combine = self.prompt.replace("{summaries}", docs_together)
|
||||
messages_combine = [{"role": "system", "content": p_chat_combine}]
|
||||
for doc in docs:
|
||||
yield {"source": doc}
|
||||
|
||||
if len(self.chat_history) > 0:
|
||||
for i in self.chat_history:
|
||||
if "prompt" in i and "response" in i:
|
||||
messages_combine.append({"role": "user", "content": i["prompt"]})
|
||||
messages_combine.append(
|
||||
{"role": "assistant", "content": i["response"]}
|
||||
)
|
||||
messages_combine.append({"role": "user", "content": self.question})
|
||||
|
||||
llm = LLMCreator.create_llm(
|
||||
settings.LLM_PROVIDER,
|
||||
api_key=settings.API_KEY,
|
||||
user_api_key=self.user_api_key,
|
||||
decoded_token=self.decoded_token,
|
||||
)
|
||||
|
||||
completion = llm.gen_stream(model=self.gpt_model, messages=messages_combine)
|
||||
for line in completion:
|
||||
yield {"answer": str(line)}
|
||||
|
||||
def search(self, query: str = ""):
|
||||
if query:
|
||||
self.question = query
|
||||
return self._get_data()
|
||||
|
||||
def get_params(self):
|
||||
return {
|
||||
"question": self.question,
|
||||
"source": self.source,
|
||||
"chat_history": self.chat_history,
|
||||
"prompt": self.prompt,
|
||||
"chunks": self.chunks,
|
||||
"token_limit": self.token_limit,
|
||||
"gpt_model": self.gpt_model,
|
||||
"user_api_key": self.user_api_key,
|
||||
}
|
||||
@@ -1,9 +1,13 @@
|
||||
from application.retriever.classic_rag import ClassicRAG
|
||||
from application.retriever.duckduck_search import DuckDuckSearch
|
||||
from application.retriever.brave_search import BraveRetSearch
|
||||
|
||||
|
||||
class RetrieverCreator:
|
||||
retrievers = {
|
||||
"classic": ClassicRAG,
|
||||
"duckduck_search": DuckDuckSearch,
|
||||
"brave_search": BraveRetSearch,
|
||||
"default": ClassicRAG,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
"""Base storage class for file system abstraction."""
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import BinaryIO, List, Callable
|
||||
|
||||
@@ -8,7 +7,7 @@ class BaseStorage(ABC):
|
||||
"""Abstract base class for storage implementations."""
|
||||
|
||||
@abstractmethod
|
||||
def save_file(self, file_data: BinaryIO, path: str, **kwargs) -> dict:
|
||||
def save_file(self, file_data: BinaryIO, path: str) -> dict:
|
||||
"""
|
||||
Save a file to storage.
|
||||
|
||||
|
||||
@@ -38,17 +38,9 @@ class S3Storage(BaseStorage):
|
||||
region_name=region_name,
|
||||
)
|
||||
|
||||
def save_file(
|
||||
self,
|
||||
file_data: BinaryIO,
|
||||
path: str,
|
||||
storage_class: str = "INTELLIGENT_TIERING",
|
||||
**kwargs,
|
||||
) -> dict:
|
||||
def save_file(self, file_data: BinaryIO, path: str) -> dict:
|
||||
"""Save a file to S3 storage."""
|
||||
self.s3.upload_fileobj(
|
||||
file_data, self.bucket_name, path, ExtraArgs={"StorageClass": storage_class}
|
||||
)
|
||||
self.s3.upload_fileobj(file_data, self.bucket_name, path)
|
||||
|
||||
region = getattr(settings, "SAGEMAKER_REGION", None)
|
||||
|
||||
|
||||
@@ -2,13 +2,5 @@
|
||||
"basics": {
|
||||
"title": "🤖 Agent Basics",
|
||||
"href": "/Agents/basics"
|
||||
},
|
||||
"api": {
|
||||
"title": "🔌 Agent API",
|
||||
"href": "/Agents/api"
|
||||
},
|
||||
"webhooks": {
|
||||
"title": "🪝 Agent Webhooks",
|
||||
"href": "/Agents/webhooks"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,227 +0,0 @@
|
||||
---
|
||||
title: Interacting with Agents via API
|
||||
description: Learn how to programmatically interact with DocsGPT Agents using the streaming and non-streaming API endpoints.
|
||||
---
|
||||
|
||||
import { Callout, Tabs } from 'nextra/components';
|
||||
|
||||
# Interacting with Agents via API
|
||||
|
||||
DocsGPT Agents can be accessed programmatically through a dedicated API, allowing you to integrate their specialized capabilities into your own applications, scripts, and workflows. This guide covers the two primary methods for interacting with an agent: the streaming API for real-time responses and the non-streaming API for a single, consolidated answer.
|
||||
|
||||
When you use an API key generated for a specific agent, you do not need to pass `prompt`, `tools` etc. The agent's configuration (including its prompt, selected tools, and knowledge sources) is already associated with its unique API key.
|
||||
|
||||
### API Endpoints
|
||||
|
||||
- **Non-Streaming:** `http://localhost:7091/api/answer`
|
||||
- **Streaming:** `http://localhost:7091/stream`
|
||||
|
||||
<Callout type="info">
|
||||
For DocsGPT Cloud, use `https://gptcloud.arc53.com/` as the base URL.
|
||||
</Callout>
|
||||
|
||||
For more technical details, you can explore the API swagger documentation available for the cloud version or your local instance.
|
||||
|
||||
---
|
||||
|
||||
## Non-Streaming API (`/api/answer`)
|
||||
|
||||
This is a standard synchronous endpoint. It waits for the agent to fully process the request and returns a single JSON object with the complete answer. This is the simplest method and is ideal for backend processes where a real-time feed is not required.
|
||||
|
||||
### Request
|
||||
|
||||
- **Endpoint:** `/api/answer`
|
||||
- **Method:** `POST`
|
||||
- **Payload:**
|
||||
- `question` (string, required): The user's query or input for the agent.
|
||||
- `api_key` (string, required): The unique API key for the agent you wish to interact with.
|
||||
- `history` (string, optional): A JSON string representing the conversation history, e.g., `[{\"prompt\": \"first question\", \"answer\": \"first answer\"}]`.
|
||||
|
||||
### Response
|
||||
|
||||
A single JSON object containing:
|
||||
- `answer`: The complete, final answer from the agent.
|
||||
- `sources`: A list of sources the agent consulted.
|
||||
- `conversation_id`: The unique ID for the interaction.
|
||||
|
||||
### Examples
|
||||
|
||||
<Tabs items={['cURL', 'Python', 'JavaScript']}>
|
||||
<Tabs.Tab>
|
||||
```bash
|
||||
curl -X POST http://localhost:7091/api/answer \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"question": "your question here",
|
||||
"api_key": "your_agent_api_key"
|
||||
}'
|
||||
```
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab>
|
||||
```python
|
||||
import requests
|
||||
|
||||
API_URL = "http://localhost:7091/api/answer"
|
||||
API_KEY = "your_agent_api_key"
|
||||
QUESTION = "your question here"
|
||||
|
||||
response = requests.post(
|
||||
API_URL,
|
||||
json={"question": QUESTION, "api_key": API_KEY}
|
||||
)
|
||||
|
||||
if response.status_code == 200:
|
||||
print(response.json())
|
||||
else:
|
||||
print(f"Error: {response.status_code}")
|
||||
print(response.text)
|
||||
```
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab>
|
||||
```javascript
|
||||
const apiUrl = 'http://localhost:7091/api/answer';
|
||||
const apiKey = 'your_agent_api_key';
|
||||
const question = 'your question here';
|
||||
|
||||
async function getAnswer() {
|
||||
try {
|
||||
const response = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ question, api_key: apiKey }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
console.log(data);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch answer:", error);
|
||||
}
|
||||
}
|
||||
|
||||
getAnswer();
|
||||
```
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## Streaming API (`/stream`)
|
||||
|
||||
The `/stream` endpoint uses Server-Sent Events (SSE) to push data in real-time. This is ideal for applications where you want to display the response as it's being generated, such as in a live chatbot interface.
|
||||
|
||||
### Request
|
||||
|
||||
- **Endpoint:** `/stream`
|
||||
- **Method:** `POST`
|
||||
- **Payload:** Same as the non-streaming API.
|
||||
|
||||
### Response (SSE Stream)
|
||||
|
||||
The stream consists of multiple `data:` events, each containing a JSON object. Your client should listen for these events and process them based on their `type`.
|
||||
|
||||
**Event Types:**
|
||||
- `answer`: A chunk of the agent's final answer.
|
||||
- `source`: A document or source used by the agent.
|
||||
- `thought`: A reasoning step from the agent (for ReAct agents).
|
||||
- `id`: The unique `conversation_id` for the interaction.
|
||||
- `error`: An error message.
|
||||
- `end`: A final message indicating the stream has concluded.
|
||||
|
||||
### Examples
|
||||
|
||||
<Tabs items={['cURL', 'Python', 'JavaScript']}>
|
||||
<Tabs.Tab>
|
||||
```bash
|
||||
curl -X POST http://localhost:7091/stream \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Accept: text/event-stream" \
|
||||
-d '{
|
||||
"question": "your question here",
|
||||
"api_key": "your_agent_api_key"
|
||||
}'
|
||||
```
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab>
|
||||
```python
|
||||
import requests
|
||||
import json
|
||||
|
||||
API_URL = "http://localhost:7091/stream"
|
||||
payload = {
|
||||
"question": "your question here",
|
||||
"api_key": "your_agent_api_key"
|
||||
}
|
||||
|
||||
with requests.post(API_URL, json=payload, stream=True) as r:
|
||||
for line in r.iter_lines():
|
||||
if line:
|
||||
decoded_line = line.decode('utf-8')
|
||||
if decoded_line.startswith('data: '):
|
||||
try:
|
||||
data = json.loads(decoded_line[6:])
|
||||
print(data)
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
```
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab>
|
||||
```javascript
|
||||
const apiUrl = 'http://localhost:7091/stream';
|
||||
const apiKey = 'your_agent_api_key';
|
||||
const question = 'your question here';
|
||||
|
||||
async function getStream() {
|
||||
try {
|
||||
const response = await fetch(apiUrl, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'text/event-stream'
|
||||
},
|
||||
// Corrected line: 'apiKey' is changed to 'api_key'
|
||||
body: JSON.stringify({ question, api_key: apiKey }),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! Status: ${response.status}`);
|
||||
}
|
||||
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
const chunk = decoder.decode(value, { stream: true });
|
||||
// Note: This parsing method assumes each chunk contains whole lines.
|
||||
// For a more robust production implementation, buffer the chunks
|
||||
// and process them line by line.
|
||||
const lines = chunk.split('\n');
|
||||
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('data: ')) {
|
||||
try {
|
||||
const data = JSON.parse(line.substring(6));
|
||||
console.log(data);
|
||||
} catch (e) {
|
||||
console.error("Failed to parse JSON from SSE event:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch stream:", error);
|
||||
}
|
||||
}
|
||||
|
||||
getStream();
|
||||
```
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
@@ -1,152 +0,0 @@
|
||||
---
|
||||
title: Triggering Agents with Webhooks
|
||||
description: Learn how to automate and integrate DocsGPT Agents using webhooks for asynchronous task execution.
|
||||
---
|
||||
|
||||
import { Callout, Tabs } from 'nextra/components';
|
||||
|
||||
# Triggering Agents with Webhooks
|
||||
|
||||
Agent Webhooks provide a powerful mechanism to trigger an agent's execution from external systems. Unlike the direct API which provides an immediate response, webhooks are designed for **asynchronous** operations. When you call a webhook, DocsGPT enqueues the agent's task for background processing and immediately returns a `task_id`. You then use this ID to poll for the result.
|
||||
|
||||
This workflow is ideal for integrating with services that expect a quick initial response (e.g., form submissions) or for triggering long-running tasks without tying up a client connection.
|
||||
|
||||
Each agent has its own unique webhook URL, which can be generated from the agent's edit page in the DocsGPT UI. This URL includes a secure token for authentication.
|
||||
|
||||
### API Endpoints
|
||||
|
||||
- **Webhook URL:** `http://localhost:7091/api/webhooks/agents/{AGENT_WEBHOOK_TOKEN}`
|
||||
- **Task Status URL:** `http://localhost:7091/api/task_status`
|
||||
|
||||
<Callout type="info">
|
||||
For DocsGPT Cloud, use `https://gptcloud.arc53.com/` as the base URL.
|
||||
</Callout>
|
||||
|
||||
For more technical details, you can explore the API swagger documentation available for the cloud version or your local instance.
|
||||
|
||||
---
|
||||
|
||||
## The Webhook Workflow
|
||||
|
||||
The process involves two main steps: triggering the task and polling for the result.
|
||||
|
||||
### Step 1: Trigger the Webhook
|
||||
|
||||
Send an HTTP `POST` request to the agent's unique webhook URL with the required payload. The structure of this payload should match what the agent's prompt and tools are designed to handle.
|
||||
|
||||
- **Method:** `POST`
|
||||
- **Response:** A JSON object with a `task_id`. `{"task_id": "a1b2c3d4-e5f6-..."}`
|
||||
|
||||
<Tabs items={['cURL', 'Python', 'JavaScript']}>
|
||||
<Tabs.Tab>
|
||||
```bash
|
||||
curl -X POST \
|
||||
http://localhost:7091/api/webhooks/agents/your_webhook_token \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"question": "Your message to agent"}'
|
||||
```
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab>
|
||||
```python
|
||||
import requests
|
||||
|
||||
WEBHOOK_URL = "http://localhost:7091/api/webhooks/agents/your_webhook_token"
|
||||
payload = {"question": "Your message to agent"}
|
||||
|
||||
try:
|
||||
response = requests.post(WEBHOOK_URL, json=payload)
|
||||
response.raise_for_status()
|
||||
task_id = response.json().get("task_id")
|
||||
print(f"Task successfully created with ID: {task_id}")
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f"Error triggering webhook: {e}")
|
||||
```
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab>
|
||||
```javascript
|
||||
const webhookUrl = 'http://localhost:7091/api/webhooks/agents/your_webhook_token';
|
||||
const payload = { question: 'Your message to agent' };
|
||||
|
||||
async function triggerWebhook() {
|
||||
try {
|
||||
const response = await fetch(webhookUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
if (!response.ok) throw new Error(`HTTP error! ${response.status}`);
|
||||
const data = await response.json();
|
||||
console.log(`Task successfully created with ID: ${data.task_id}`);
|
||||
return data.task_id;
|
||||
} catch (error) {
|
||||
console.error('Error triggering webhook:', error);
|
||||
}
|
||||
}
|
||||
|
||||
triggerWebhook();
|
||||
```
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
|
||||
### Step 2: Poll for the Result
|
||||
|
||||
Once you have the `task_id`, periodically send a `GET` request to the `/api/task_status` endpoint until the task `status` is `SUCCESS` or `FAILURE`.
|
||||
|
||||
- **`status`**: The current state of the task (`PENDING`, `STARTED`, `SUCCESS`, `FAILURE`).
|
||||
- **`result`**: The final output from the agent, available when the status is `SUCCESS` or `FAILURE`.
|
||||
|
||||
<Tabs items={['cURL', 'Python', 'JavaScript']}>
|
||||
<Tabs.Tab>
|
||||
```bash
|
||||
# Replace the task_id with the one you received
|
||||
curl http://localhost:7091/api/task_status?task_id=YOUR_TASK_ID
|
||||
```
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab>
|
||||
```python
|
||||
import requests
|
||||
import time
|
||||
|
||||
STATUS_URL = "http://localhost:7091/api/task_status"
|
||||
task_id = "YOUR_TASK_ID"
|
||||
|
||||
while True:
|
||||
response = requests.get(STATUS_URL, params={"task_id": task_id})
|
||||
data = response.json()
|
||||
status = data.get("status")
|
||||
print(f"Current task status: {status}")
|
||||
|
||||
if status in ["SUCCESS", "FAILURE"]:
|
||||
print("Final Result:")
|
||||
print(data.get("result"))
|
||||
break
|
||||
|
||||
time.sleep(2)
|
||||
```
|
||||
</Tabs.Tab>
|
||||
<Tabs.Tab>
|
||||
```javascript
|
||||
const statusUrl = 'http://localhost:7091/api/task_status';
|
||||
const taskId = 'YOUR_TASK_ID';
|
||||
|
||||
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
async function pollForResult() {
|
||||
while (true) {
|
||||
const response = await fetch(`${statusUrl}?task_id=${taskId}`);
|
||||
const data = await response.json();
|
||||
const status = data.status;
|
||||
console.log(`Current task status: ${status}`);
|
||||
|
||||
if (status === 'SUCCESS' || status === 'FAILURE') {
|
||||
console.log('Final Result:', data.result);
|
||||
break;
|
||||
}
|
||||
await sleep(2000);
|
||||
}
|
||||
}
|
||||
|
||||
pollForResult();
|
||||
```
|
||||
</Tabs.Tab>
|
||||
</Tabs>
|
||||
1631
frontend/package-lock.json
generated
1631
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -19,18 +19,19 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@reduxjs/toolkit": "^2.8.2",
|
||||
"@reduxjs/toolkit": "^2.5.1",
|
||||
"chart.js": "^4.4.4",
|
||||
"clsx": "^2.1.1",
|
||||
"copy-to-clipboard": "^3.3.3",
|
||||
"i18next": "^24.2.0",
|
||||
"i18next-browser-languagedetector": "^8.0.2",
|
||||
"mermaid": "^11.6.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^19.1.0",
|
||||
"react": "^18.2.0",
|
||||
"react-chartjs-2": "^5.3.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-dropzone": "^14.3.8",
|
||||
"react-copy-to-clipboard": "^5.1.0",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-dropzone": "^14.3.5",
|
||||
"react-helmet": "^6.1.0",
|
||||
"react-i18next": "^15.4.0",
|
||||
"react-markdown": "^9.0.1",
|
||||
"react-redux": "^9.2.0",
|
||||
@@ -42,14 +43,15 @@
|
||||
"tailwind-merge": "^3.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tailwindcss/postcss": "^4.1.10",
|
||||
"@types/mermaid": "^9.1.0",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.0.0",
|
||||
"@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.4",
|
||||
"autoprefixer": "^10.4.13",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-prettier": "^10.1.5",
|
||||
"eslint-config-standard-with-typescript": "^34.0.0",
|
||||
@@ -63,8 +65,8 @@
|
||||
"lint-staged": "^15.3.0",
|
||||
"postcss": "^8.4.49",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.13",
|
||||
"tailwindcss": "^4.1.10",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"tailwindcss": "^3.4.17",
|
||||
"typescript": "^5.8.3",
|
||||
"vite": "^6.3.5",
|
||||
"vite-plugin-svgr": "^4.3.0"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
'@tailwindcss/postcss': {},
|
||||
tailwindcss: {},
|
||||
autoprefixer: {},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 122.88 122.88"><defs><style>.a{fill:#d53;}.b{fill:#fff;}.c{fill:#ddd;}.d{fill:#fc0;}.e{fill:#6b5;}.f{fill:#4a4;}.g{fill:#148;}</style></defs><title>duckduckgo</title><path class="a" d="M122.88,61.44a61.44,61.44,0,1,0-61.44,61.44,61.44,61.44,0,0,0,61.44-61.44Z"/><path class="b" d="M114.37,61.44a52.92,52.92,0,1,0-15.5,37.43,52.76,52.76,0,0,0,15.5-37.43Zm-13.12-39.8A56.29,56.29,0,1,1,61.44,5.15a56.12,56.12,0,0,1,39.81,16.49Z"/><path class="c" d="M43.24,30.15C26.17,34.13,32.43,58,32.43,58l10.81,52.9,4,1.71-4-82.49Zm-4-10.24H34.7L41,22.19s-6.26,0-6.26,4C48.36,25.6,54.61,29,54.61,29l-15.36-9.1Zm0,0Z"/><path class="b" d="M75.66,115.48S62,93.87,62,79.64c0-26.73,17.63-4,17.63-25S62,28.44,62,28.44c-8.53-10.8-25-8.53-25-8.53l4,2.28s-4,1.13-5.12,2.27,10.81-1.7,15.93,2.85C30.72,29,34.13,46.08,34.13,46.08l11.95,68.27,29.58,1.13Zm0,0Z"/><path class="d" d="M75.66,60.87l21.62-5.69C116.62,58,80.78,68.84,78.51,68.27c-17.07-2.85-12,11.37,8.53,6.82s5.12,11.38-13.65,5.12c-26.74-7.39-12.52-20.48,2.27-19.34Z"/><path class="e" d="M70,105.81l1.14-1.7c12.52,4.55,13.09,6.25,12.52-5.12s0-11.38-13.09-1.71c0-2.84-7.39-1.71-8.53,0-11.95-5.12-13.09-6.83-12.52,1.14,1.14,16.5.57,13.65,11.95,8l8.53-.57Zm0,0Z"/><path class="f" d="M60.87,99.56v6.82c.57,1.14,9.67,1.14,9.67-1.14s-4.55,1.71-7.39.57S62,98.42,62,98.42l-1.14,1.14Zm0,0Z"/><path class="g" d="M48.36,43.24c-2.85-3.42-10.24-.57-8.54,4,.57-2.28,4.55-5.69,8.54-4Zm18.2,0c.57-3.42,6.26-4,8-.57a8,8,0,0,0-8,.57Zm-18.77,9.1a1.14,1.14,0,1,1,0,.57v-.57Zm-4.55,2.27a4,4,0,1,0,0-.57v.57Zm29.58-4a1.14,1.14,0,1,1,0,.57v-.57ZM69.4,52.91a3.42,3.42,0,1,0,0-.57v.57Zm0,0Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 1.6 KiB |
BIN
frontend/signal-desktop-keyring.gpg
Normal file
BIN
frontend/signal-desktop-keyring.gpg
Normal file
Binary file not shown.
@@ -19,9 +19,9 @@ export default function Hero({
|
||||
}>;
|
||||
|
||||
return (
|
||||
<div className="text-black-1000 dark:text-bright-gray flex h-full w-full flex-col items-center justify-between">
|
||||
<div className="flex h-full w-full flex-col items-center justify-between text-black-1000 dark:text-bright-gray">
|
||||
{/* Header Section */}
|
||||
<div className="flex grow flex-col items-center justify-center pt-8 md:pt-0">
|
||||
<div className="flex flex-grow flex-col items-center justify-center pt-8 md:pt-0">
|
||||
<div className="mb-4 flex items-center">
|
||||
<span className="text-4xl font-semibold">DocsGPT</span>
|
||||
<img className="mb-1 inline w-14" src={DocsGPT3} alt="docsgpt" />
|
||||
@@ -38,9 +38,9 @@ export default function Hero({
|
||||
<button
|
||||
key={key}
|
||||
onClick={() => handleQuestion({ question: demo.query })}
|
||||
className={`border-dark-gray text-just-black hover:bg-cultured dark:border-dim-gray dark:text-chinese-white dark:hover:bg-charleston-green w-full rounded-[66px] border bg-transparent px-6 py-[14px] text-left transition-colors ${key >= 2 ? 'hidden md:block' : ''} // Show only 2 buttons on mobile`}
|
||||
className={`w-full rounded-[66px] border border-dark-gray bg-transparent px-6 py-[14px] text-left text-just-black transition-colors hover:bg-cultured dark:border-dim-gray dark:text-chinese-white dark:hover:bg-charleston-green ${key >= 2 ? 'hidden md:block' : ''} // Show only 2 buttons on mobile`}
|
||||
>
|
||||
<p className="text-black-1000 dark:text-bright-gray mb-2 font-semibold">
|
||||
<p className="mb-2 font-semibold text-black-1000 dark:text-bright-gray">
|
||||
{demo.header}
|
||||
</p>
|
||||
<span className="line-clamp-2 text-gray-700 opacity-60 dark:text-gray-300">
|
||||
|
||||
@@ -81,27 +81,8 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
useState<ActiveState>('INACTIVE');
|
||||
const [recentAgents, setRecentAgents] = useState<Agent[]>([]);
|
||||
|
||||
const navRef = useRef<HTMLDivElement>(null);
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (
|
||||
navRef.current &&
|
||||
!navRef.current.contains(event.target as Node) &&
|
||||
(isMobile || isTablet) &&
|
||||
navOpen
|
||||
) {
|
||||
setNavOpen(false);
|
||||
}
|
||||
}
|
||||
const navRef = useRef(null);
|
||||
|
||||
//event listener only for mobile/tablet when nav is open
|
||||
if ((isMobile || isTablet) && navOpen) {
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
return () => {
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}
|
||||
}, [navOpen, isMobile, isTablet, setNavOpen]);
|
||||
async function fetchRecentAgents() {
|
||||
try {
|
||||
const response = await userService.getPinnedAgents(token);
|
||||
@@ -293,7 +274,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
return (
|
||||
<>
|
||||
{!navOpen && (
|
||||
<div className="absolute top-3 left-3 z-20 hidden transition-all duration-25 lg:block">
|
||||
<div className="duration-25 absolute left-3 top-3 z-20 hidden transition-all lg:block">
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={() => {
|
||||
@@ -321,7 +302,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
<div className="text-gray-4000 text-[20px] font-medium">
|
||||
<div className="text-[20px] font-medium text-[#949494]">
|
||||
DocsGPT
|
||||
</div>
|
||||
</div>
|
||||
@@ -330,8 +311,8 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
<div
|
||||
ref={navRef}
|
||||
className={`${
|
||||
!navOpen && '-ml-96 md:-ml-72'
|
||||
} bg-lotion dark:border-r-purple-taupe dark:bg-chinese-black fixed top-0 z-20 flex h-full w-72 flex-col border-r border-b-0 transition-all duration-20 dark:text-white`}
|
||||
!navOpen && '-ml-96 md:-ml-[18rem]'
|
||||
} duration-20 fixed top-0 z-20 flex h-full w-72 flex-col border-b-0 border-r-[1px] bg-lotion transition-all dark:border-r-purple-taupe dark:bg-chinese-black dark:text-white`}
|
||||
>
|
||||
<div
|
||||
className={'visible mt-2 flex h-[6vh] w-full justify-between md:h-12'}
|
||||
@@ -375,7 +356,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
className={({ isActive }) =>
|
||||
`${
|
||||
isActive ? 'bg-transparent' : ''
|
||||
} group border-silver hover:border-rainy-gray dark:border-purple-taupe sticky mx-4 mt-4 flex cursor-pointer gap-2.5 rounded-3xl border p-3 hover:bg-transparent dark:text-white`
|
||||
} group sticky mx-4 mt-4 flex cursor-pointer gap-2.5 rounded-3xl border border-silver p-3 hover:border-rainy-gray hover:bg-transparent dark:border-purple-taupe dark:text-white`
|
||||
}
|
||||
>
|
||||
<img
|
||||
@@ -383,16 +364,16 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
alt="Create new chat"
|
||||
className="opacity-80 group-hover:opacity-100"
|
||||
/>
|
||||
<p className="text-dove-gray dark:text-chinese-silver dark:group-hover:text-bright-gray text-sm group-hover:text-neutral-600">
|
||||
<p className="text-sm text-dove-gray group-hover:text-neutral-600 dark:text-chinese-silver dark:group-hover:text-bright-gray">
|
||||
{t('newChat')}
|
||||
</p>
|
||||
</NavLink>
|
||||
<div
|
||||
id="conversationsMainDiv"
|
||||
className="mb-auto h-[78vh] overflow-x-hidden overflow-y-auto dark:text-white"
|
||||
className="mb-auto h-[78vh] overflow-y-auto overflow-x-hidden dark:text-white"
|
||||
>
|
||||
{conversations?.loading && !isDeletingConversation && (
|
||||
<div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transform">
|
||||
<div className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 transform">
|
||||
<img
|
||||
src={isDarkTheme ? SpinnerDark : Spinner}
|
||||
className="animate-spin cursor-pointer bg-transparent"
|
||||
@@ -403,14 +384,14 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
{recentAgents?.length > 0 ? (
|
||||
<div>
|
||||
<div className="mx-4 my-auto mt-2 flex h-6 items-center">
|
||||
<p className="mt-1 ml-4 text-sm font-semibold">Agents</p>
|
||||
<p className="ml-4 mt-1 text-sm font-semibold">Agents</p>
|
||||
</div>
|
||||
<div className="agents-container">
|
||||
<div>
|
||||
{recentAgents.map((agent, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className={`group hover:bg-bright-gray dark:hover:bg-dark-charcoal mx-4 my-auto mt-4 flex h-9 cursor-pointer items-center justify-between rounded-3xl pl-4 ${
|
||||
className={`group mx-4 my-auto mt-4 flex h-9 cursor-pointer items-center justify-between rounded-3xl pl-4 hover:bg-bright-gray dark:hover:bg-dark-charcoal ${
|
||||
agent.id === selectedAgent?.id && !conversationId
|
||||
? 'bg-bright-gray dark:bg-dark-charcoal'
|
||||
: ''
|
||||
@@ -429,7 +410,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
className="h-6 w-6 rounded-full object-contain"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-eerie-black dark:text-bright-gray overflow-hidden text-sm leading-6 text-ellipsis whitespace-nowrap">
|
||||
<p className="overflow-hidden overflow-ellipsis whitespace-nowrap text-sm leading-6 text-eerie-black dark:text-bright-gray">
|
||||
{agent.name}
|
||||
</p>
|
||||
</div>
|
||||
@@ -453,7 +434,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
className="hover:bg-bright-gray dark:hover:bg-dark-charcoal mx-4 my-auto mt-2 flex h-9 cursor-pointer items-center gap-2 rounded-3xl pl-4"
|
||||
className="mx-4 my-auto mt-2 flex h-9 cursor-pointer items-center gap-2 rounded-3xl pl-4 hover:bg-bright-gray dark:hover:bg-dark-charcoal"
|
||||
onClick={() => {
|
||||
dispatch(setSelectedAgent(null));
|
||||
if (isMobile || isTablet) {
|
||||
@@ -469,7 +450,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
className="h-[18px] w-[18px]"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-eerie-black dark:text-bright-gray overflow-hidden text-sm leading-6 text-ellipsis whitespace-nowrap">
|
||||
<p className="overflow-hidden overflow-ellipsis whitespace-nowrap text-sm leading-6 text-eerie-black dark:text-bright-gray">
|
||||
{t('manageAgents')}
|
||||
</p>
|
||||
</div>
|
||||
@@ -477,7 +458,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
</div>
|
||||
) : (
|
||||
<div
|
||||
className="hover:bg-bright-gray dark:hover:bg-dark-charcoal mx-4 my-auto mt-2 flex h-9 cursor-pointer items-center gap-2 rounded-3xl pl-4"
|
||||
className="mx-4 my-auto mt-2 flex h-9 cursor-pointer items-center gap-2 rounded-3xl pl-4 hover:bg-bright-gray dark:hover:bg-dark-charcoal"
|
||||
onClick={() => {
|
||||
if (isMobile || isTablet) {
|
||||
setNavOpen(false);
|
||||
@@ -493,7 +474,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
className="h-[18px] w-[18px]"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-eerie-black dark:text-bright-gray overflow-hidden text-sm leading-6 text-ellipsis whitespace-nowrap">
|
||||
<p className="overflow-hidden overflow-ellipsis whitespace-nowrap text-sm leading-6 text-eerie-black dark:text-bright-gray">
|
||||
{t('manageAgents')}
|
||||
</p>
|
||||
</div>
|
||||
@@ -501,7 +482,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
{conversations?.data && conversations.data.length > 0 ? (
|
||||
<div className="mt-7">
|
||||
<div className="mx-4 my-auto mt-2 flex h-6 items-center justify-between gap-4 rounded-3xl">
|
||||
<p className="mt-1 ml-4 text-sm font-semibold">{t('chats')}</p>
|
||||
<p className="ml-4 mt-1 text-sm font-semibold">{t('chats')}</p>
|
||||
</div>
|
||||
<div className="conversations-container">
|
||||
{conversations.data?.map((conversation) => (
|
||||
@@ -526,8 +507,8 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-eerie-black flex h-auto flex-col justify-end dark:text-white">
|
||||
<div className="dark:border-b-purple-taupe flex flex-col gap-2 border-b py-2">
|
||||
<div className="flex h-auto flex-col justify-end text-eerie-black dark:text-white">
|
||||
<div className="flex flex-col gap-2 border-b-[1px] py-2 dark:border-b-purple-taupe">
|
||||
<NavLink
|
||||
onClick={() => {
|
||||
if (isMobile || isTablet) {
|
||||
@@ -537,7 +518,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
}}
|
||||
to="/settings"
|
||||
className={({ isActive }) =>
|
||||
`mx-4 my-auto flex h-9 cursor-pointer items-center gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-[#28292E] ${
|
||||
`mx-4 my-auto flex h-9 cursor-pointer gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-[#28292E] ${
|
||||
isActive ? 'bg-gray-3000 dark:bg-transparent' : ''
|
||||
}`
|
||||
}
|
||||
@@ -545,16 +526,14 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
<img
|
||||
src={SettingGear}
|
||||
alt="Settings"
|
||||
width={21}
|
||||
height={21}
|
||||
className="my-auto ml-2 filter dark:invert"
|
||||
className="w- ml-2 filter dark:invert"
|
||||
/>
|
||||
<p className="text-eerie-black text-sm dark:text-white">
|
||||
<p className="my-auto text-sm text-eerie-black dark:text-white">
|
||||
{t('settings.label')}
|
||||
</p>
|
||||
</NavLink>
|
||||
</div>
|
||||
<div className="text-eerie-black flex flex-col justify-end dark:text-white">
|
||||
<div className="flex flex-col justify-end text-eerie-black dark:text-white">
|
||||
<div className="flex items-center justify-between py-1">
|
||||
<Help />
|
||||
|
||||
@@ -603,7 +582,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="dark:border-b-purple-taupe dark:bg-chinese-black sticky z-10 h-16 w-full border-b-2 bg-gray-50 lg:hidden">
|
||||
<div className="sticky z-10 h-16 w-full border-b-2 bg-gray-50 dark:border-b-purple-taupe dark:bg-chinese-black lg:hidden">
|
||||
<div className="ml-6 flex h-full items-center gap-6">
|
||||
<button
|
||||
className="h-6 w-6 lg:hidden"
|
||||
@@ -615,7 +594,7 @@ export default function Navigation({ navOpen, setNavOpen }: NavigationProps) {
|
||||
className="w-7 filter dark:invert"
|
||||
/>
|
||||
</button>
|
||||
<div className="text-gray-4000 text-[20px] font-medium">DocsGPT</div>
|
||||
<div className="text-[20px] font-medium text-[#949494]">DocsGPT</div>
|
||||
</div>
|
||||
</div>
|
||||
<DeleteConvModal
|
||||
|
||||
@@ -54,7 +54,7 @@ export default function AgentCard({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`relative flex h-44 w-48 flex-col justify-between rounded-[1.2rem] bg-[#F6F6F6] px-6 py-5 hover:bg-[#ECECEC] dark:bg-[#383838] dark:hover:bg-[#383838]/80 ${
|
||||
className={`relative flex h-44 w-48 flex-col justify-between rounded-[1.2rem] bg-[#F6F6F6] px-6 py-5 hover:bg-[#ECECEC] dark:bg-[#383838] hover:dark:bg-[#383838]/80 ${
|
||||
agent.status === 'published' ? 'cursor-pointer' : ''
|
||||
}`}
|
||||
onClick={handleCardClick}
|
||||
@@ -65,7 +65,7 @@ export default function AgentCard({
|
||||
e.stopPropagation();
|
||||
setIsMenuOpen(true);
|
||||
}}
|
||||
className="absolute top-4 right-4 z-10 cursor-pointer"
|
||||
className="absolute right-4 top-4 z-10 cursor-pointer"
|
||||
>
|
||||
<img src={ThreeDots} alt="options" className="h-[19px] w-[19px]" />
|
||||
{menuOptions && (
|
||||
@@ -96,11 +96,11 @@ export default function AgentCard({
|
||||
<div className="mt-2">
|
||||
<p
|
||||
title={agent.name}
|
||||
className="truncate px-1 text-[13px] leading-relaxed font-semibold text-[#020617] capitalize dark:text-[#E0E0E0]"
|
||||
className="truncate px-1 text-[13px] font-semibold capitalize leading-relaxed text-[#020617] dark:text-[#E0E0E0]"
|
||||
>
|
||||
{agent.name}
|
||||
</p>
|
||||
<p className="dark:text-sonic-silver-light mt-1 h-20 overflow-auto px-1 text-[12px] leading-relaxed text-[#64748B]">
|
||||
<p className="mt-1 h-20 overflow-auto px-1 text-[12px] leading-relaxed text-[#64748B] dark:text-sonic-silver-light">
|
||||
{agent.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -44,12 +44,12 @@ export default function AgentLogs() {
|
||||
>
|
||||
<img src={ArrowLeft} alt="left-arrow" className="h-3 w-3" />
|
||||
</button>
|
||||
<p className="text-eerie-black dark:text-bright-gray mt-px text-sm font-semibold">
|
||||
<p className="mt-px text-sm font-semibold text-eerie-black dark:text-bright-gray">
|
||||
Back to all agents
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-5 flex w-full flex-wrap items-center justify-between gap-2 px-4">
|
||||
<h1 className="text-eerie-black m-0 text-[40px] font-bold dark:text-white">
|
||||
<h1 className="m-0 text-[40px] font-bold text-[#212121] dark:text-white">
|
||||
Agent Logs
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
@@ -6,23 +6,24 @@ import ConversationMessages from '../conversation/ConversationMessages';
|
||||
import { Query } from '../conversation/conversationModels';
|
||||
import {
|
||||
addQuery,
|
||||
fetchPreviewAnswer,
|
||||
handlePreviewAbort,
|
||||
fetchAnswer,
|
||||
handleAbort,
|
||||
resendQuery,
|
||||
resetPreview,
|
||||
selectPreviewQueries,
|
||||
selectPreviewStatus,
|
||||
} from './agentPreviewSlice';
|
||||
resetConversation,
|
||||
selectQueries,
|
||||
selectStatus,
|
||||
} from '../conversation/conversationSlice';
|
||||
import { selectSelectedAgent } from '../preferences/preferenceSlice';
|
||||
import { AppDispatch } from '../store';
|
||||
|
||||
export default function AgentPreview() {
|
||||
const dispatch = useDispatch<AppDispatch>();
|
||||
|
||||
const queries = useSelector(selectPreviewQueries);
|
||||
const status = useSelector(selectPreviewStatus);
|
||||
const queries = useSelector(selectQueries);
|
||||
const status = useSelector(selectStatus);
|
||||
const selectedAgent = useSelector(selectSelectedAgent);
|
||||
|
||||
const [input, setInput] = useState('');
|
||||
const [lastQueryReturnedErr, setLastQueryReturnedErr] = useState(false);
|
||||
|
||||
const fetchStream = useRef<any>(null);
|
||||
@@ -30,7 +31,7 @@ export default function AgentPreview() {
|
||||
const handleFetchAnswer = useCallback(
|
||||
({ question, index }: { question: string; index?: number }) => {
|
||||
fetchStream.current = dispatch(
|
||||
fetchPreviewAnswer({ question, indx: index }),
|
||||
fetchAnswer({ question, indx: index, isPreview: true }),
|
||||
);
|
||||
},
|
||||
[dispatch],
|
||||
@@ -94,11 +95,11 @@ export default function AgentPreview() {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(resetPreview());
|
||||
dispatch(resetConversation());
|
||||
return () => {
|
||||
if (fetchStream.current) fetchStream.current.abort();
|
||||
handlePreviewAbort();
|
||||
dispatch(resetPreview());
|
||||
handleAbort();
|
||||
dispatch(resetConversation());
|
||||
};
|
||||
}, [dispatch]);
|
||||
|
||||
|
||||
@@ -295,24 +295,24 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
>
|
||||
<img src={ArrowLeft} alt="left-arrow" className="h-3 w-3" />
|
||||
</button>
|
||||
<p className="text-eerie-black dark:text-bright-gray mt-px text-sm font-semibold">
|
||||
<p className="mt-px text-sm font-semibold text-eerie-black dark:text-bright-gray">
|
||||
Back to all agents
|
||||
</p>
|
||||
</div>
|
||||
<div className="mt-5 flex w-full flex-wrap items-center justify-between gap-2 px-4">
|
||||
<h1 className="text-eerie-black m-0 text-[40px] font-bold dark:text-white">
|
||||
<h1 className="m-0 text-[40px] font-bold text-[#212121] dark:text-white">
|
||||
{modeConfig[effectiveMode].heading}
|
||||
</h1>
|
||||
<div className="flex flex-wrap items-center gap-1">
|
||||
<button
|
||||
className="text-purple-30 dark:text-light-gray mr-4 rounded-3xl py-2 text-sm font-medium dark:bg-transparent"
|
||||
className="mr-4 rounded-3xl py-2 text-sm font-medium text-purple-30 dark:bg-transparent dark:text-light-gray"
|
||||
onClick={handleCancel}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
{modeConfig[effectiveMode].showDelete && agent.id && (
|
||||
<button
|
||||
className="group border-red-2000 text-red-2000 hover:bg-red-2000 flex items-center gap-2 rounded-3xl border border-solid px-5 py-2 text-sm font-medium transition-colors hover:text-white"
|
||||
className="group flex items-center gap-2 rounded-3xl border border-solid border-red-2000 px-5 py-2 text-sm font-medium text-red-2000 transition-colors hover:bg-red-2000 hover:text-white"
|
||||
onClick={() => setDeleteConfirmation('ACTIVE')}
|
||||
>
|
||||
<span className="block h-4 w-4 bg-[url('/src/assets/red-trash.svg')] bg-contain bg-center bg-no-repeat transition-all group-hover:bg-[url('/src/assets/white-trash.svg')]" />
|
||||
@@ -321,7 +321,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
)}
|
||||
{modeConfig[effectiveMode].showSaveDraft && (
|
||||
<button
|
||||
className="hover:bg-vi</button>olets-are-blue border-violets-are-blue text-violets-are-blue hover:bg-violets-are-blue rounded-3xl border border-solid px-5 py-2 text-sm font-medium transition-colors hover:text-white"
|
||||
className="hover:bg-vi</button>olets-are-blue rounded-3xl border border-solid border-violets-are-blue px-5 py-2 text-sm font-medium text-violets-are-blue transition-colors hover:bg-violets-are-blue hover:text-white"
|
||||
onClick={handleSaveDraft}
|
||||
>
|
||||
Save Draft
|
||||
@@ -329,7 +329,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
)}
|
||||
{modeConfig[effectiveMode].showAccessDetails && (
|
||||
<button
|
||||
className="group border-violets-are-blue text-violets-are-blue hover:bg-violets-are-blue flex items-center gap-2 rounded-3xl border border-solid px-5 py-2 text-sm font-medium transition-colors hover:text-white"
|
||||
className="group flex items-center gap-2 rounded-3xl border border-solid border-violets-are-blue px-5 py-2 text-sm font-medium text-violets-are-blue transition-colors hover:bg-violets-are-blue hover:text-white"
|
||||
onClick={() => navigate(`/agents/logs/${agent.id}`)}
|
||||
>
|
||||
<span className="block h-5 w-5 bg-[url('/src/assets/monitoring-purple.svg')] bg-contain bg-center bg-no-repeat transition-all group-hover:bg-[url('/src/assets/monitoring-white.svg')]" />
|
||||
@@ -338,7 +338,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
)}
|
||||
{modeConfig[effectiveMode].showAccessDetails && (
|
||||
<button
|
||||
className="hover:bg-vi</button>olets-are-blue border-violets-are-blue text-violets-are-blue hover:bg-violets-are-blue rounded-3xl border border-solid px-5 py-2 text-sm font-medium transition-colors hover:text-white"
|
||||
className="hover:bg-vi</button>olets-are-blue rounded-3xl border border-solid border-violets-are-blue px-5 py-2 text-sm font-medium text-violets-are-blue transition-colors hover:bg-violets-are-blue hover:text-white"
|
||||
onClick={() => setAgentDetails('ACTIVE')}
|
||||
>
|
||||
Access Details
|
||||
@@ -346,7 +346,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
)}
|
||||
<button
|
||||
disabled={!isPublishable()}
|
||||
className={`${!isPublishable() && 'cursor-not-allowed opacity-30'} bg-purple-30 hover:bg-violets-are-blue rounded-3xl px-5 py-2 text-sm font-medium text-white`}
|
||||
className={`${!isPublishable() && 'cursor-not-allowed opacity-30'} rounded-3xl bg-purple-30 px-5 py-2 text-sm font-medium text-white hover:bg-violets-are-blue`}
|
||||
onClick={handlePublish}
|
||||
>
|
||||
Publish
|
||||
@@ -358,14 +358,14 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
<div className="rounded-[30px] bg-[#F6F6F6] px-6 py-3 dark:bg-[#383838] dark:text-[#E0E0E0]">
|
||||
<h2 className="text-lg font-semibold">Meta</h2>
|
||||
<input
|
||||
className="border-silver text-jet dark:bg-raisin-black dark:text-bright-gray dark:placeholder:text-silver mt-3 w-full rounded-3xl border bg-white px-5 py-3 text-sm outline-hidden placeholder:text-gray-400 dark:border-[#7E7E7E]"
|
||||
className="mt-3 w-full rounded-3xl border border-silver bg-white px-5 py-3 text-sm text-jet outline-none placeholder:text-gray-400 dark:border-[#7E7E7E] dark:bg-[#222327] dark:text-bright-gray placeholder:dark:text-silver"
|
||||
type="text"
|
||||
value={agent.name}
|
||||
placeholder="Agent name"
|
||||
onChange={(e) => setAgent({ ...agent, name: e.target.value })}
|
||||
/>
|
||||
<textarea
|
||||
className="border-silver text-jet dark:bg-raisin-black dark:text-bright-gray dark:placeholder:text-silver mt-3 h-32 w-full rounded-3xl border bg-white px-5 py-4 text-sm outline-hidden placeholder:text-gray-400 dark:border-[#7E7E7E]"
|
||||
className="mt-3 h-32 w-full rounded-3xl border border-silver bg-white px-5 py-4 text-sm text-jet outline-none placeholder:text-gray-400 dark:border-[#7E7E7E] dark:bg-[#222327] dark:text-bright-gray placeholder:dark:text-silver"
|
||||
placeholder="Describe your agent"
|
||||
value={agent.description}
|
||||
onChange={(e) =>
|
||||
@@ -375,7 +375,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
<div className="mt-3">
|
||||
<FileUpload
|
||||
showPreview
|
||||
className="dark:bg-raisin-black"
|
||||
className="dark:bg-[#222327]"
|
||||
onUpload={handleUpload}
|
||||
onRemove={() => setImageFile(null)}
|
||||
uploadText={[
|
||||
@@ -395,10 +395,10 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
<button
|
||||
ref={sourceAnchorButtonRef}
|
||||
onClick={() => setIsSourcePopupOpen(!isSourcePopupOpen)}
|
||||
className={`border-silver dark:bg-raisin-black w-full truncate rounded-3xl border bg-white px-5 py-3 text-left text-sm dark:border-[#7E7E7E] ${
|
||||
className={`w-full truncate rounded-3xl border border-silver bg-white px-5 py-3 text-left text-sm dark:border-[#7E7E7E] dark:bg-[#222327] ${
|
||||
selectedSourceIds.size > 0
|
||||
? 'text-jet dark:text-bright-gray'
|
||||
: 'dark:text-silver text-gray-400'
|
||||
: 'text-gray-400 dark:text-silver'
|
||||
}`}
|
||||
>
|
||||
{selectedSourceIds.size > 0
|
||||
@@ -447,11 +447,12 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
}
|
||||
size="w-full"
|
||||
rounded="3xl"
|
||||
buttonDarkBackgroundColor="[#222327]"
|
||||
border="border"
|
||||
buttonClassName="bg-white dark:bg-[#222327] border-silver dark:border-[#7E7E7E]"
|
||||
optionsClassName="bg-white dark:bg-[#383838] border-silver dark:border-[#7E7E7E]"
|
||||
darkBorderColor="[#7E7E7E]"
|
||||
placeholder="Chunks per query"
|
||||
placeholderClassName="text-gray-400 dark:text-silver"
|
||||
placeholderTextColor="gray-400"
|
||||
darkPlaceholderTextColor="silver"
|
||||
contentSize="text-sm"
|
||||
/>
|
||||
</div>
|
||||
@@ -460,7 +461,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
<div className="rounded-[30px] bg-[#F6F6F6] px-6 py-3 dark:bg-[#383838] dark:text-[#E0E0E0]">
|
||||
<h2 className="text-lg font-semibold">Prompt</h2>
|
||||
<div className="mt-3 flex flex-wrap items-center gap-1">
|
||||
<div className="min-w-20 grow basis-full sm:basis-0">
|
||||
<div className="min-w-20 flex-grow basis-full sm:basis-0">
|
||||
<Dropdown
|
||||
options={prompts.map((prompt) => ({
|
||||
label: prompt.name,
|
||||
@@ -478,16 +479,17 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
}
|
||||
size="w-full"
|
||||
rounded="3xl"
|
||||
buttonDarkBackgroundColor="[#222327]"
|
||||
border="border"
|
||||
buttonClassName="bg-white dark:bg-[#222327] border-silver dark:border-[#7E7E7E]"
|
||||
optionsClassName="bg-white dark:bg-[#383838] border-silver dark:border-[#7E7E7E] dark:border-[#7E7E7E] dark:bg-dark-charcoal"
|
||||
placeholderClassName="text-gray-400 dark:text-silver"
|
||||
darkBorderColor="[#7E7E7E]"
|
||||
placeholder="Select a prompt"
|
||||
placeholderTextColor="gray-400"
|
||||
darkPlaceholderTextColor="silver"
|
||||
contentSize="text-sm"
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="border-violets-are-blue text-violets-are-blue hover:bg-violets-are-blue w-20 shrink-0 basis-full rounded-3xl border-2 border-solid px-5 py-[11px] text-sm transition-colors hover:text-white sm:basis-auto"
|
||||
className="w-20 flex-shrink-0 basis-full rounded-3xl border-2 border-solid border-violets-are-blue px-5 py-[11px] text-sm text-violets-are-blue transition-colors hover:bg-violets-are-blue hover:text-white sm:basis-auto"
|
||||
onClick={() => setAddPromptModal('ACTIVE')}
|
||||
>
|
||||
Add
|
||||
@@ -500,10 +502,10 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
<button
|
||||
ref={toolAnchorButtonRef}
|
||||
onClick={() => setIsToolsPopupOpen(!isToolsPopupOpen)}
|
||||
className={`border-silver dark:bg-raisin-black w-full truncate rounded-3xl border bg-white px-5 py-3 text-left text-sm dark:border-[#7E7E7E] ${
|
||||
className={`w-full truncate rounded-3xl border border-silver bg-white px-5 py-3 text-left text-sm dark:border-[#7E7E7E] dark:bg-[#222327] ${
|
||||
selectedToolIds.size > 0
|
||||
? 'text-jet dark:text-bright-gray'
|
||||
: 'dark:text-silver text-gray-400'
|
||||
: 'text-gray-400 dark:text-silver'
|
||||
}`}
|
||||
>
|
||||
{selectedToolIds.size > 0
|
||||
@@ -546,11 +548,12 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
}
|
||||
size="w-full"
|
||||
rounded="3xl"
|
||||
buttonDarkBackgroundColor="[#222327]"
|
||||
border="border"
|
||||
buttonClassName="bg-white dark:bg-[#222327] border-silver dark:border-[#7E7E7E]"
|
||||
optionsClassName="bg-white dark:bg-[#383838] border-silver dark:border-[#7E7E7E]"
|
||||
darkBorderColor="[#7E7E7E]"
|
||||
placeholder="Select type"
|
||||
placeholderClassName="text-gray-400 dark:text-silver"
|
||||
placeholderTextColor="gray-400"
|
||||
darkPlaceholderTextColor="silver"
|
||||
contentSize="text-sm"
|
||||
/>
|
||||
</div>
|
||||
@@ -595,7 +598,7 @@ export default function NewAgent({ mode }: { mode: 'new' | 'edit' | 'draft' }) {
|
||||
function AgentPreviewArea() {
|
||||
const selectedAgent = useSelector(selectSelectedAgent);
|
||||
return (
|
||||
<div className="dark:bg-raisin-black h-full w-full rounded-[30px] border border-[#F6F6F6] bg-white max-[1180px]:h-192 dark:border-[#7E7E7E]">
|
||||
<div className="h-full w-full rounded-[30px] border border-[#F6F6F6] bg-white dark:border-[#7E7E7E] dark:bg-[#222327] max-[1180px]:h-[48rem]">
|
||||
{selectedAgent?.status === 'published' ? (
|
||||
<div className="flex h-full w-full flex-col justify-end overflow-auto rounded-[30px]">
|
||||
<AgentPreview />
|
||||
@@ -603,7 +606,7 @@ function AgentPreviewArea() {
|
||||
) : (
|
||||
<div className="flex h-full w-full flex-col items-center justify-center gap-2">
|
||||
<span className="block h-12 w-12 bg-[url('/src/assets/science-spark.svg')] bg-contain bg-center bg-no-repeat transition-all dark:bg-[url('/src/assets/science-spark-dark.svg')]" />{' '}
|
||||
<p className="dark:text-gray-4000 text-xs text-[#18181B]">
|
||||
<p className="text-xs text-[#18181B] dark:text-[#949494]">
|
||||
Published agents can be previewed here
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -57,7 +57,9 @@ export default function SharedAgent() {
|
||||
|
||||
const handleFetchAnswer = useCallback(
|
||||
({ question, index }: { question: string; index?: number }) => {
|
||||
fetchStream.current = dispatch(fetchAnswer({ question, indx: index }));
|
||||
fetchStream.current = dispatch(
|
||||
fetchAnswer({ question, indx: index, isPreview: false }),
|
||||
);
|
||||
},
|
||||
[dispatch],
|
||||
);
|
||||
@@ -143,7 +145,7 @@ export default function SharedAgent() {
|
||||
alt="No agent found"
|
||||
className="mx-auto mb-6 h-32 w-32"
|
||||
/>
|
||||
<p className="dark:text-gray-4000 text-center text-lg text-[#71717A]">
|
||||
<p className="text-center text-lg text-[#71717A] dark:text-[#949494]">
|
||||
No agent found. Please ensure the agent is shared.
|
||||
</p>
|
||||
</div>
|
||||
@@ -151,7 +153,7 @@ export default function SharedAgent() {
|
||||
);
|
||||
return (
|
||||
<div className="relative h-full w-full">
|
||||
<div className="absolute top-5 left-4 hidden items-center gap-3 sm:flex">
|
||||
<div className="absolute left-4 top-5 hidden items-center gap-3 sm:flex">
|
||||
<img
|
||||
src={
|
||||
sharedAgent.image && sharedAgent.image.trim() !== ''
|
||||
@@ -161,7 +163,7 @@ export default function SharedAgent() {
|
||||
alt="agent-logo"
|
||||
className="h-6 w-6 rounded-full object-contain"
|
||||
/>
|
||||
<h2 className="text-eerie-black text-lg font-semibold dark:text-[#E0E0E0]">
|
||||
<h2 className="text-lg font-semibold text-[#212121] dark:text-[#E0E0E0]">
|
||||
{sharedAgent.name}
|
||||
</h2>
|
||||
</div>
|
||||
@@ -188,7 +190,7 @@ export default function SharedAgent() {
|
||||
showToolButton={sharedAgent ? false : true}
|
||||
autoFocus={false}
|
||||
/>
|
||||
<p className="text-gray-4000 dark:text-sonic-silver hidden w-screen self-center bg-transparent py-2 text-center text-xs md:inline md:w-full">
|
||||
<p className="hidden w-[100vw] self-center bg-transparent py-2 text-center text-xs text-gray-4000 dark:text-sonic-silver md:inline md:w-full">
|
||||
{t('tagline')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { Agent } from './types';
|
||||
|
||||
export default function SharedAgentCard({ agent }: { agent: Agent }) {
|
||||
return (
|
||||
<div className="border-dark-gray dark:border-grey flex w-full max-w-[720px] flex-col rounded-3xl border p-6 shadow-xs sm:w-fit sm:min-w-[480px]">
|
||||
<div className="flex w-full max-w-[720px] flex-col rounded-3xl border border-dark-gray p-6 shadow-sm dark:border-grey sm:w-fit sm:min-w-[480px]">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="flex h-12 w-12 items-center justify-center overflow-hidden rounded-full p-1">
|
||||
<img
|
||||
@@ -12,10 +12,10 @@ export default function SharedAgentCard({ agent }: { agent: Agent }) {
|
||||
/>
|
||||
</div>
|
||||
<div className="flex max-h-[92px] w-[80%] flex-col gap-px">
|
||||
<h2 className="text-eerie-black text-base font-semibold sm:text-lg dark:text-[#E0E0E0]">
|
||||
<h2 className="text-base font-semibold text-[#212121] dark:text-[#E0E0E0] sm:text-lg">
|
||||
{agent.name}
|
||||
</h2>
|
||||
<p className="dark:text-gray-4000 overflow-y-auto text-xs text-wrap break-all text-[#71717A] sm:text-sm">
|
||||
<p className="overflow-y-auto text-wrap break-all text-xs text-[#71717A] dark:text-[#949494] sm:text-sm">
|
||||
{agent.description}
|
||||
</p>
|
||||
</div>
|
||||
@@ -23,12 +23,12 @@ export default function SharedAgentCard({ agent }: { agent: Agent }) {
|
||||
{agent.shared_metadata && (
|
||||
<div className="mt-4 flex items-center gap-8">
|
||||
{agent.shared_metadata?.shared_by && (
|
||||
<p className="text-eerie-black text-xs font-light sm:text-sm dark:text-[#E0E0E0]">
|
||||
<p className="text-xs font-light text-[#212121] dark:text-[#E0E0E0] sm:text-sm">
|
||||
by {agent.shared_metadata.shared_by}
|
||||
</p>
|
||||
)}
|
||||
{agent.shared_metadata?.shared_at && (
|
||||
<p className="dark:text-gray-4000 text-xs font-light text-[#71717A] sm:text-sm">
|
||||
<p className="text-xs font-light text-[#71717A] dark:text-[#949494] sm:text-sm">
|
||||
Shared on{' '}
|
||||
{new Date(agent.shared_metadata.shared_at).toLocaleString(
|
||||
'en-US',
|
||||
@@ -47,14 +47,14 @@ export default function SharedAgentCard({ agent }: { agent: Agent }) {
|
||||
)}
|
||||
{agent.tool_details && agent.tool_details.length > 0 && (
|
||||
<div className="mt-8">
|
||||
<p className="text-eerie-black text-sm font-semibold sm:text-base dark:text-[#E0E0E0]">
|
||||
<p className="text-sm font-semibold text-[#212121] dark:text-[#E0E0E0] sm:text-base">
|
||||
Connected Tools
|
||||
</p>
|
||||
<div className="mt-2 flex flex-wrap gap-2">
|
||||
{agent.tool_details.map((tool, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="bg-bright-gray text-eerie-black dark:bg-dark-charcoal flex items-center gap-1 rounded-full px-3 py-1 text-xs font-light dark:text-[#E0E0E0]"
|
||||
className="flex items-center gap-1 rounded-full bg-bright-gray px-3 py-1 text-xs font-light text-[#212121] dark:bg-dark-charcoal dark:text-[#E0E0E0]"
|
||||
>
|
||||
<img
|
||||
src={`/toolIcons/tool_${tool.name}.svg`}
|
||||
|
||||
@@ -1,319 +0,0 @@
|
||||
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import {
|
||||
Answer,
|
||||
ConversationState,
|
||||
Query,
|
||||
Status,
|
||||
} from '../conversation/conversationModels';
|
||||
import {
|
||||
handleFetchAnswer,
|
||||
handleFetchAnswerSteaming,
|
||||
} from '../conversation/conversationHandlers';
|
||||
import {
|
||||
selectCompletedAttachments,
|
||||
clearAttachments,
|
||||
} from '../upload/uploadSlice';
|
||||
import store from '../store';
|
||||
|
||||
const initialState: ConversationState = {
|
||||
queries: [],
|
||||
status: 'idle',
|
||||
conversationId: null,
|
||||
};
|
||||
|
||||
const API_STREAMING = import.meta.env.VITE_API_STREAMING === 'true';
|
||||
|
||||
let abortController: AbortController | null = null;
|
||||
export function handlePreviewAbort() {
|
||||
if (abortController) {
|
||||
abortController.abort();
|
||||
abortController = null;
|
||||
}
|
||||
}
|
||||
|
||||
export const fetchPreviewAnswer = createAsyncThunk<
|
||||
Answer,
|
||||
{ question: string; indx?: number }
|
||||
>(
|
||||
'agentPreview/fetchAnswer',
|
||||
async ({ question, indx }, { dispatch, getState }) => {
|
||||
if (abortController) abortController.abort();
|
||||
abortController = new AbortController();
|
||||
const { signal } = abortController;
|
||||
|
||||
const state = getState() as RootState;
|
||||
const attachmentIds = selectCompletedAttachments(state)
|
||||
.filter((a) => a.id)
|
||||
.map((a) => a.id) as string[];
|
||||
|
||||
if (attachmentIds.length > 0) {
|
||||
dispatch(clearAttachments());
|
||||
}
|
||||
|
||||
if (state.preference) {
|
||||
if (API_STREAMING) {
|
||||
await handleFetchAnswerSteaming(
|
||||
question,
|
||||
signal,
|
||||
state.preference.token,
|
||||
state.preference.selectedDocs!,
|
||||
state.agentPreview.queries,
|
||||
null, // No conversation ID for previews
|
||||
state.preference.prompt.id,
|
||||
state.preference.chunks,
|
||||
state.preference.token_limit,
|
||||
(event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
const targetIndex = indx ?? state.agentPreview.queries.length - 1;
|
||||
|
||||
if (data.type === 'end') {
|
||||
dispatch(agentPreviewSlice.actions.setStatus('idle'));
|
||||
} else if (data.type === 'thought') {
|
||||
dispatch(
|
||||
updateThought({
|
||||
index: targetIndex,
|
||||
query: { thought: data.thought },
|
||||
}),
|
||||
);
|
||||
} else if (data.type === 'source') {
|
||||
dispatch(
|
||||
updateStreamingSource({
|
||||
index: targetIndex,
|
||||
query: { sources: data.source ?? [] },
|
||||
}),
|
||||
);
|
||||
} else if (data.type === 'tool_call') {
|
||||
dispatch(
|
||||
updateToolCall({
|
||||
index: targetIndex,
|
||||
tool_call: data.data,
|
||||
}),
|
||||
);
|
||||
} else if (data.type === 'error') {
|
||||
dispatch(agentPreviewSlice.actions.setStatus('failed'));
|
||||
dispatch(
|
||||
agentPreviewSlice.actions.raiseError({
|
||||
index: targetIndex,
|
||||
message: data.error,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
dispatch(
|
||||
updateStreamingQuery({
|
||||
index: targetIndex,
|
||||
query: { response: data.answer },
|
||||
}),
|
||||
);
|
||||
}
|
||||
},
|
||||
indx,
|
||||
state.preference.selectedAgent?.id,
|
||||
attachmentIds,
|
||||
false, // Don't save preview conversations
|
||||
);
|
||||
} else {
|
||||
// Non-streaming implementation
|
||||
const answer = await handleFetchAnswer(
|
||||
question,
|
||||
signal,
|
||||
state.preference.token,
|
||||
state.preference.selectedDocs!,
|
||||
state.agentPreview.queries,
|
||||
null, // No conversation ID for previews
|
||||
state.preference.prompt.id,
|
||||
state.preference.chunks,
|
||||
state.preference.token_limit,
|
||||
state.preference.selectedAgent?.id,
|
||||
attachmentIds,
|
||||
false, // Don't save preview conversations
|
||||
);
|
||||
|
||||
if (answer) {
|
||||
const 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;
|
||||
},
|
||||
);
|
||||
|
||||
const targetIndex = indx ?? state.agentPreview.queries.length - 1;
|
||||
|
||||
dispatch(
|
||||
updateQuery({
|
||||
index: targetIndex,
|
||||
query: {
|
||||
response: answer.answer,
|
||||
thought: answer.thought,
|
||||
sources: sourcesPrepped,
|
||||
tool_calls: answer.toolCalls,
|
||||
},
|
||||
}),
|
||||
);
|
||||
dispatch(agentPreviewSlice.actions.setStatus('idle'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
conversationId: null,
|
||||
title: null,
|
||||
answer: '',
|
||||
query: question,
|
||||
result: '',
|
||||
thought: '',
|
||||
sources: [],
|
||||
tool_calls: [],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
export const agentPreviewSlice = createSlice({
|
||||
name: 'agentPreview',
|
||||
initialState,
|
||||
reducers: {
|
||||
addQuery(state, action: PayloadAction<Query>) {
|
||||
state.queries.push(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>;
|
||||
}>,
|
||||
) {
|
||||
const { index, query } = action.payload;
|
||||
if (state.status === 'idle') return;
|
||||
|
||||
if (query.response != undefined) {
|
||||
state.queries[index].response =
|
||||
(state.queries[index].response || '') + query.response;
|
||||
}
|
||||
},
|
||||
updateThought(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
index: number;
|
||||
query: Partial<Query>;
|
||||
}>,
|
||||
) {
|
||||
const { index, query } = action.payload;
|
||||
if (query.thought != undefined) {
|
||||
state.queries[index].thought =
|
||||
(state.queries[index].thought || '') + query.thought;
|
||||
}
|
||||
},
|
||||
updateStreamingSource(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
index: number;
|
||||
query: Partial<Query>;
|
||||
}>,
|
||||
) {
|
||||
const { index, query } = action.payload;
|
||||
if (!state.queries[index].sources) {
|
||||
state.queries[index].sources = query?.sources;
|
||||
} else if (query.sources) {
|
||||
state.queries[index].sources!.push(...query.sources);
|
||||
}
|
||||
},
|
||||
updateToolCall(state, action) {
|
||||
const { index, tool_call } = action.payload;
|
||||
|
||||
if (!state.queries[index].tool_calls) {
|
||||
state.queries[index].tool_calls = [];
|
||||
}
|
||||
|
||||
const existingIndex = state.queries[index].tool_calls.findIndex(
|
||||
(call) => call.call_id === tool_call.call_id,
|
||||
);
|
||||
|
||||
if (existingIndex !== -1) {
|
||||
const existingCall = state.queries[index].tool_calls[existingIndex];
|
||||
state.queries[index].tool_calls[existingIndex] = {
|
||||
...existingCall,
|
||||
...tool_call,
|
||||
};
|
||||
} else state.queries[index].tool_calls.push(tool_call);
|
||||
},
|
||||
updateQuery(
|
||||
state,
|
||||
action: PayloadAction<{ index: number; query: Partial<Query> }>,
|
||||
) {
|
||||
const { index, query } = action.payload;
|
||||
state.queries[index] = {
|
||||
...state.queries[index],
|
||||
...query,
|
||||
};
|
||||
},
|
||||
setStatus(state, action: PayloadAction<Status>) {
|
||||
state.status = action.payload;
|
||||
},
|
||||
raiseError(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
index: number;
|
||||
message: string;
|
||||
}>,
|
||||
) {
|
||||
const { index, message } = action.payload;
|
||||
state.queries[index].error = message;
|
||||
},
|
||||
resetPreview: (state) => {
|
||||
state.queries = initialState.queries;
|
||||
state.status = initialState.status;
|
||||
state.conversationId = initialState.conversationId;
|
||||
handlePreviewAbort();
|
||||
},
|
||||
},
|
||||
extraReducers(builder) {
|
||||
builder
|
||||
.addCase(fetchPreviewAnswer.pending, (state) => {
|
||||
state.status = 'loading';
|
||||
})
|
||||
.addCase(fetchPreviewAnswer.rejected, (state, action) => {
|
||||
if (action.meta.aborted) {
|
||||
state.status = 'idle';
|
||||
return state;
|
||||
}
|
||||
state.status = 'failed';
|
||||
state.queries[state.queries.length - 1].error = 'Something went wrong';
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
type RootState = ReturnType<typeof store.getState>;
|
||||
|
||||
export const selectPreviewQueries = (state: RootState) =>
|
||||
state.agentPreview.queries;
|
||||
export const selectPreviewStatus = (state: RootState) =>
|
||||
state.agentPreview.status;
|
||||
|
||||
export const {
|
||||
addQuery,
|
||||
updateQuery,
|
||||
resendQuery,
|
||||
updateStreamingQuery,
|
||||
updateThought,
|
||||
updateStreamingSource,
|
||||
updateToolCall,
|
||||
setStatus,
|
||||
raiseError,
|
||||
resetPreview,
|
||||
} = agentPreviewSlice.actions;
|
||||
|
||||
export default agentPreviewSlice.reducer;
|
||||
@@ -111,10 +111,10 @@ function AgentsList() {
|
||||
}, [token]);
|
||||
return (
|
||||
<div className="p-4 md:p-12">
|
||||
<h1 className="text-eerie-black mb-0 text-[40px] font-bold dark:text-[#E0E0E0]">
|
||||
<h1 className="mb-0 text-[40px] font-bold text-[#212121] dark:text-[#E0E0E0]">
|
||||
Agents
|
||||
</h1>
|
||||
<p className="dark:text-gray-4000 mt-5 text-[15px] text-[#71717A]">
|
||||
<p className="mt-5 text-[15px] text-[#71717A] dark:text-[#949494]">
|
||||
Discover and create custom versions of DocsGPT that combine
|
||||
instructions, extra knowledge, and any combination of skills
|
||||
</p>
|
||||
@@ -206,7 +206,7 @@ function AgentSection({
|
||||
</div>
|
||||
{sectionConfig[section].showNewAgentButton && (
|
||||
<button
|
||||
className="bg-purple-30 hover:bg-violets-are-blue rounded-full px-4 py-2 text-sm text-white"
|
||||
className="rounded-full bg-purple-30 px-4 py-2 text-sm text-white hover:bg-violets-are-blue"
|
||||
onClick={() => navigate('/agents/new')}
|
||||
>
|
||||
New Agent
|
||||
@@ -235,7 +235,7 @@ function AgentSection({
|
||||
<p>{sectionConfig[section].emptyStateDescription}</p>
|
||||
{sectionConfig[section].showNewAgentButton && (
|
||||
<button
|
||||
className="bg-purple-30 hover:bg-violets-are-blue ml-2 rounded-full px-4 py-2 text-sm text-white"
|
||||
className="ml-2 rounded-full bg-purple-30 px-4 py-2 text-sm text-white hover:bg-violets-are-blue"
|
||||
onClick={() => navigate('/agents/new')}
|
||||
>
|
||||
New Agent
|
||||
@@ -410,7 +410,7 @@ function AgentCard({
|
||||
};
|
||||
return (
|
||||
<div
|
||||
className={`relative flex h-44 w-full flex-col justify-between rounded-[1.2rem] bg-[#F6F6F6] px-6 py-5 hover:bg-[#ECECEC] md:w-48 dark:bg-[#383838] dark:hover:bg-[#383838]/80 ${agent.status === 'published' && 'cursor-pointer'}`}
|
||||
className={`relative flex h-44 w-full flex-col justify-between rounded-[1.2rem] bg-[#F6F6F6] px-6 py-5 hover:bg-[#ECECEC] dark:bg-[#383838] hover:dark:bg-[#383838]/80 md:w-48 ${agent.status === 'published' && 'cursor-pointer'}`}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleClick();
|
||||
@@ -422,7 +422,7 @@ function AgentCard({
|
||||
e.stopPropagation();
|
||||
setIsMenuOpen(true);
|
||||
}}
|
||||
className="absolute top-4 right-4 z-10 cursor-pointer"
|
||||
className="absolute right-4 top-4 z-10 cursor-pointer"
|
||||
>
|
||||
<img src={ThreeDots} alt={'use-agent'} className="h-[19px] w-[19px]" />
|
||||
<ContextMenu
|
||||
@@ -448,11 +448,11 @@ function AgentCard({
|
||||
<div className="mt-2">
|
||||
<p
|
||||
title={agent.name}
|
||||
className="truncate px-1 text-[13px] leading-relaxed font-semibold text-[#020617] capitalize dark:text-[#E0E0E0]"
|
||||
className="truncate px-1 text-[13px] font-semibold capitalize leading-relaxed text-[#020617] dark:text-[#E0E0E0]"
|
||||
>
|
||||
{agent.name}
|
||||
</p>
|
||||
<p className="dark:text-sonic-silver-light mt-1 h-20 overflow-auto px-1 text-[12px] leading-relaxed text-[#64748B]">
|
||||
<p className="mt-1 h-20 overflow-auto px-1 text-[12px] leading-relaxed text-[#64748B] dark:text-sonic-silver-light">
|
||||
{agent.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -32,9 +32,9 @@ export default function Accordion({
|
||||
setIsOpen(!isOpen);
|
||||
};
|
||||
return (
|
||||
<div className={`overflow-hidden shadow-xs ${className}`}>
|
||||
<div className={`overflow-hidden shadow-sm ${className}`}>
|
||||
<button
|
||||
className={`flex w-full items-center justify-between focus:outline-hidden ${titleClassName}`}
|
||||
className={`flex w-full items-center justify-between focus:outline-none ${titleClassName}`}
|
||||
onClick={toggleAccordion}
|
||||
>
|
||||
<p className="break-words">{title}</p>
|
||||
|
||||
@@ -9,5 +9,5 @@ export default function Avatar({
|
||||
size?: 'SMALL' | 'MEDIUM' | 'LARGE';
|
||||
className: string;
|
||||
}) {
|
||||
return <div className={`${className} shrink-0`}>{avatar}</div>;
|
||||
return <div className={`${className} flex-shrink-0`}>{avatar}</div>;
|
||||
}
|
||||
|
||||
@@ -14,10 +14,10 @@ interface ContextMenuProps {
|
||||
isOpen: boolean;
|
||||
setIsOpen: (isOpen: boolean) => void;
|
||||
options: MenuOption[];
|
||||
anchorRef: React.RefObject<HTMLDivElement | null>;
|
||||
position?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';
|
||||
offset?: { x: number; y: number };
|
||||
anchorRef: React.RefObject<HTMLElement>;
|
||||
className?: string;
|
||||
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
||||
offset?: { x: number; y: number };
|
||||
}
|
||||
|
||||
export default function ContextMenu({
|
||||
@@ -125,7 +125,7 @@ export default function ContextMenu({
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div
|
||||
className="bg-lotion dark:bg-charleston-green-2 flex flex-col rounded-xl text-sm shadow-xl"
|
||||
className="flex flex-col rounded-xl bg-lotion text-sm shadow-xl dark:bg-charleston-green-2"
|
||||
style={{ minWidth: '144px' }}
|
||||
>
|
||||
{options.map((option, index) => (
|
||||
@@ -144,7 +144,7 @@ export default function ContextMenu({
|
||||
} `}
|
||||
>
|
||||
{option.icon && (
|
||||
<div className="flex w-4 min-w-4 shrink-0 justify-center">
|
||||
<div className="flex w-4 min-w-4 flex-shrink-0 justify-center">
|
||||
<img
|
||||
width={option.iconWidth || 16}
|
||||
height={option.iconHeight || 16}
|
||||
@@ -154,7 +154,7 @@ export default function ContextMenu({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<span className="break-words hyphens-auto">{option.label}</span>
|
||||
<span className="hyphens-auto break-words">{option.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -61,7 +61,7 @@ export default function CopyButton({
|
||||
|
||||
const rootButtonClasses = clsx(
|
||||
'flex items-center gap-2 group',
|
||||
'focus:outline-hidden focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500 rounded-full',
|
||||
'focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500 rounded-full',
|
||||
className,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
interface DocumentHeadProps {
|
||||
title?: string;
|
||||
description?: string;
|
||||
keywords?: string;
|
||||
ogTitle?: string;
|
||||
ogDescription?: string;
|
||||
ogImage?: string;
|
||||
twitterCard?: string;
|
||||
twitterTitle?: string;
|
||||
twitterDescription?: string;
|
||||
children?: React.ReactNode;
|
||||
}
|
||||
|
||||
export function DocumentHead({
|
||||
title,
|
||||
description,
|
||||
keywords,
|
||||
ogTitle,
|
||||
ogDescription,
|
||||
ogImage,
|
||||
twitterCard,
|
||||
twitterTitle,
|
||||
twitterDescription,
|
||||
children,
|
||||
}: DocumentHeadProps) {
|
||||
return (
|
||||
<>
|
||||
{title && <title>{title}</title>}
|
||||
{description && <meta name="description" content={description} />}
|
||||
{keywords && <meta name="keywords" content={keywords} />}
|
||||
|
||||
{/* Open Graph */}
|
||||
{ogTitle && <meta property="og:title" content={ogTitle} />}
|
||||
{ogDescription && (
|
||||
<meta property="og:description" content={ogDescription} />
|
||||
)}
|
||||
{ogImage && <meta property="og:image" content={ogImage} />}
|
||||
|
||||
{/* Twitter */}
|
||||
{twitterCard && <meta name="twitter:card" content={twitterCard} />}
|
||||
{twitterTitle && <meta name="twitter:title" content={twitterTitle} />}
|
||||
{twitterDescription && (
|
||||
<meta name="twitter:description" content={twitterDescription} />
|
||||
)}
|
||||
|
||||
{/* Additional elements */}
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -10,15 +10,20 @@ function Dropdown({
|
||||
onSelect,
|
||||
size = 'w-32',
|
||||
rounded = 'xl',
|
||||
buttonClassName = 'border-silver bg-white dark:bg-transparent dark:border-dim-gray',
|
||||
optionsClassName = 'border-silver bg-white dark:border-dim-gray dark:bg-dark-charcoal',
|
||||
buttonBackgroundColor = 'white',
|
||||
buttonDarkBackgroundColor = 'transparent',
|
||||
optionsBackgroundColor = 'white',
|
||||
optionsDarkBackgroundColor = 'dark-charcoal',
|
||||
border = 'border-2',
|
||||
borderColor = 'silver',
|
||||
darkBorderColor = 'dim-gray',
|
||||
showEdit,
|
||||
onEdit,
|
||||
showDelete,
|
||||
onDelete,
|
||||
placeholder,
|
||||
placeholderClassName = 'text-gray-500 dark:text-gray-400',
|
||||
placeholderTextColor = 'gray-500',
|
||||
darkPlaceholderTextColor = 'gray-400',
|
||||
contentSize = 'text-base',
|
||||
}: {
|
||||
options:
|
||||
@@ -39,15 +44,20 @@ function Dropdown({
|
||||
| ((value: { value: number; description: string }) => void);
|
||||
size?: string;
|
||||
rounded?: 'xl' | '3xl';
|
||||
buttonClassName?: string;
|
||||
optionsClassName?: string;
|
||||
buttonBackgroundColor?: string;
|
||||
buttonDarkBackgroundColor?: string;
|
||||
optionsBackgroundColor?: string;
|
||||
optionsDarkBackgroundColor?: string;
|
||||
border?: 'border' | 'border-2';
|
||||
borderColor?: string;
|
||||
darkBorderColor?: string;
|
||||
showEdit?: boolean;
|
||||
onEdit?: (value: { name: string; id: string; type: string }) => void;
|
||||
showDelete?: boolean | ((option: any) => boolean);
|
||||
showDelete?: boolean;
|
||||
onDelete?: (value: string) => void;
|
||||
placeholder?: string;
|
||||
placeholderClassName?: string;
|
||||
placeholderTextColor?: string;
|
||||
darkPlaceholderTextColor?: string;
|
||||
contentSize?: string;
|
||||
}) {
|
||||
const dropdownRef = React.useRef<HTMLDivElement>(null);
|
||||
@@ -70,7 +80,6 @@ function Dropdown({
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
@@ -83,18 +92,19 @@ function Dropdown({
|
||||
>
|
||||
<button
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
className={`flex w-full cursor-pointer items-center justify-between ${border} ${buttonClassName} px-5 py-3 ${
|
||||
className={`flex w-full cursor-pointer items-center justify-between ${border} border-${borderColor} bg-${buttonBackgroundColor} px-5 py-3 dark:border-${darkBorderColor} dark:bg-${buttonDarkBackgroundColor} ${
|
||||
isOpen ? `${borderTopRadius}` : `${borderRadius}`
|
||||
}`}
|
||||
>
|
||||
{typeof selectedValue === 'string' ? (
|
||||
<span className="dark:text-bright-gray truncate">
|
||||
<span className="truncate dark:text-bright-gray">
|
||||
{selectedValue}
|
||||
</span>
|
||||
) : (
|
||||
<span
|
||||
className={`truncate ${selectedValue && `dark:text-bright-gray`} ${
|
||||
!selectedValue && ` ${placeholderClassName}`
|
||||
!selectedValue &&
|
||||
`text-${placeholderTextColor} dark:text-${darkPlaceholderTextColor}`
|
||||
} ${contentSize}`}
|
||||
>
|
||||
{selectedValue && 'label' in selectedValue
|
||||
@@ -120,7 +130,7 @@ function Dropdown({
|
||||
</button>
|
||||
{isOpen && (
|
||||
<div
|
||||
className={`absolute right-0 left-0 z-20 -mt-1 max-h-40 overflow-y-auto rounded-b-xl ${border} ${optionsClassName} shadow-lg`}
|
||||
className={`absolute left-0 right-0 z-20 -mt-1 max-h-40 overflow-y-auto rounded-b-xl ${border} border-${borderColor} bg-${optionsBackgroundColor} shadow-lg dark:border-${darkBorderColor} dark:bg-${optionsDarkBackgroundColor}`}
|
||||
>
|
||||
{options.map((option: any, index) => (
|
||||
<div
|
||||
@@ -132,7 +142,7 @@ function Dropdown({
|
||||
onSelect(option);
|
||||
setIsOpen(false);
|
||||
}}
|
||||
className={`dark:text-light-gray ml-5 flex-1 overflow-hidden py-3 text-ellipsis whitespace-nowrap ${contentSize}`}
|
||||
className={`ml-5 flex-1 overflow-hidden overflow-ellipsis whitespace-nowrap py-3 dark:text-light-gray ${contentSize}`}
|
||||
>
|
||||
{typeof option === 'string'
|
||||
? option
|
||||
@@ -163,15 +173,8 @@ function Dropdown({
|
||||
)}
|
||||
{showDelete && onDelete && (
|
||||
<button
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
onDelete?.(typeof option === 'string' ? option : option.id);
|
||||
}}
|
||||
className={`${
|
||||
typeof showDelete === 'function' && !showDelete(option)
|
||||
? 'hidden'
|
||||
: ''
|
||||
} mr-2 h-4 w-4 cursor-pointer hover:opacity-50`}
|
||||
onClick={() => onDelete(option.id)}
|
||||
disabled={option.type === 'public'}
|
||||
>
|
||||
<img
|
||||
src={Trash}
|
||||
|
||||
@@ -7,12 +7,12 @@ type DropdownMenuProps = {
|
||||
onSelect: (value: string) => void;
|
||||
defaultValue?: string;
|
||||
icon?: string;
|
||||
isOpen: boolean;
|
||||
onOpenChange: (isOpen: boolean) => void;
|
||||
anchorRef: React.RefObject<HTMLElement | null>;
|
||||
position?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';
|
||||
offset?: { x: number; y: number };
|
||||
isOpen?: boolean;
|
||||
onOpenChange?: (isOpen: boolean) => void;
|
||||
anchorRef?: React.RefObject<HTMLElement>;
|
||||
className?: string;
|
||||
position?: 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
|
||||
offset?: { x: number; y: number };
|
||||
};
|
||||
|
||||
export default function DropdownMenu({
|
||||
|
||||
@@ -43,13 +43,13 @@ const Help = () => {
|
||||
</button>
|
||||
{isOpen && (
|
||||
<div
|
||||
className={`dark:bg-outer-space absolute z-10 w-48 translate-x-4 -translate-y-28 rounded-xl bg-white shadow-lg`}
|
||||
className={`absolute z-10 w-48 -translate-y-28 translate-x-4 rounded-xl bg-white shadow-lg dark:bg-[#444654]`}
|
||||
>
|
||||
<a
|
||||
href="https://docs.docsgpt.cloud/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="hover:bg-bright-gray flex items-start gap-4 rounded-t-xl px-4 py-2 text-black dark:text-white dark:hover:bg-[#545561]"
|
||||
className="flex items-start gap-4 rounded-t-xl px-4 py-2 text-black hover:bg-bright-gray dark:text-white dark:hover:bg-[#545561]"
|
||||
>
|
||||
<img
|
||||
src={PageIcon}
|
||||
@@ -61,7 +61,7 @@ const Help = () => {
|
||||
</a>
|
||||
<a
|
||||
href="mailto:support@docsgpt.cloud"
|
||||
className="hover:bg-bright-gray flex items-start gap-4 rounded-b-xl px-4 py-2 text-black dark:text-white dark:hover:bg-[#545561]"
|
||||
className="flex items-start gap-4 rounded-b-xl px-4 py-2 text-black hover:bg-bright-gray dark:text-white dark:hover:bg-[#545561]"
|
||||
>
|
||||
<img
|
||||
src={EmailIcon}
|
||||
|
||||
@@ -42,7 +42,7 @@ const Input = ({
|
||||
<div className={`relative ${className}`}>
|
||||
<input
|
||||
ref={inputRef}
|
||||
className={`peer text-jet dark:text-bright-gray h-[42px] w-full rounded-full bg-transparent px-3 py-1 placeholder-transparent outline-hidden ${colorStyles[colorVariant]} ${borderStyles[borderVariant]} ${textSizeStyles[textSize]} [&:-webkit-autofill]:appearance-none [&:-webkit-autofill]:bg-transparent [&:-webkit-autofill_selected]:bg-transparent`}
|
||||
className={`peer h-[42px] w-full rounded-full bg-transparent px-3 py-1 text-jet placeholder-transparent outline-none dark:text-bright-gray ${colorStyles[colorVariant]} ${borderStyles[borderVariant]} ${textSizeStyles[textSize]} [&:-webkit-autofill]:appearance-none [&:-webkit-autofill]:bg-transparent [&:-webkit-autofill_selected]:bg-transparent`}
|
||||
type={type}
|
||||
id={id}
|
||||
name={name}
|
||||
@@ -62,9 +62,9 @@ const Input = ({
|
||||
htmlFor={id}
|
||||
className={`absolute select-none ${
|
||||
hasValue ? '-top-2.5 left-3 text-xs' : ''
|
||||
} px-2 transition-all peer-placeholder-shown:top-2.5 peer-placeholder-shown:left-3 peer-placeholder-shown:${
|
||||
} px-2 transition-all peer-placeholder-shown:left-3 peer-placeholder-shown:top-2.5 peer-placeholder-shown:${
|
||||
textSizeStyles[textSize]
|
||||
} text-gray-4000 pointer-events-none cursor-none peer-focus:-top-2.5 peer-focus:left-3 peer-focus:text-xs dark:text-gray-400 ${labelBgClassName} max-w-[calc(100%-24px)] overflow-hidden text-ellipsis whitespace-nowrap`}
|
||||
} pointer-events-none cursor-none text-gray-4000 peer-focus:-top-2.5 peer-focus:left-3 peer-focus:text-xs dark:text-gray-400 ${labelBgClassName} max-w-[calc(100%-24px)] overflow-hidden text-ellipsis whitespace-nowrap`}
|
||||
>
|
||||
{placeholder}
|
||||
{required && (
|
||||
|
||||
@@ -258,9 +258,9 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
|
||||
const errorRender = !isCurrentlyLoading && error;
|
||||
|
||||
return (
|
||||
<div className="w-inherit group border-light-silver dark:border-raisin-black dark:bg-eerie-black relative rounded-lg border bg-white">
|
||||
<div className="bg-platinum dark:bg-eerie-black-2 flex items-center justify-between px-2 py-1">
|
||||
<span className="text-just-black dark:text-chinese-white text-xs font-medium">
|
||||
<div className="w-inherit group relative rounded-lg border border-light-silver bg-white dark:border-raisin-black dark:bg-eerie-black">
|
||||
<div className="flex items-center justify-between bg-platinum px-2 py-1 dark:bg-eerie-black-2">
|
||||
<span className="text-xs font-medium text-just-black dark:text-chinese-white">
|
||||
mermaid
|
||||
</span>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -270,13 +270,13 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
|
||||
<div className="relative" ref={downloadMenuRef}>
|
||||
<button
|
||||
onClick={() => setShowDownloadMenu(!showDownloadMenu)}
|
||||
className="flex h-full items-center rounded-sm bg-gray-100 px-2 py-1 text-xs dark:bg-gray-700"
|
||||
className="flex h-full items-center rounded bg-gray-100 px-2 py-1 text-xs dark:bg-gray-700"
|
||||
title="Download options"
|
||||
>
|
||||
Download <span className="ml-1">▼</span>
|
||||
</button>
|
||||
{showDownloadMenu && (
|
||||
<div className="absolute right-0 z-10 mt-1 w-40 rounded-sm border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-800">
|
||||
<div className="absolute right-0 z-10 mt-1 w-40 rounded border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-800">
|
||||
<ul>
|
||||
{downloadOptions.map((option, index) => (
|
||||
<li key={index}>
|
||||
@@ -314,14 +314,14 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
|
||||
</div>
|
||||
|
||||
{isCurrentlyLoading ? (
|
||||
<div className="dark:bg-eerie-black flex items-center justify-center bg-white p-4">
|
||||
<div className="flex items-center justify-center bg-white p-4 dark:bg-eerie-black">
|
||||
<div className="text-sm text-gray-500 dark:text-gray-400">
|
||||
Loading diagram...
|
||||
</div>
|
||||
</div>
|
||||
) : errorRender ? (
|
||||
<div className="m-2 rounded-sm border-2 border-red-400 dark:border-red-700">
|
||||
<div className="overflow-auto bg-red-100 px-4 py-2 text-sm break-words whitespace-normal text-red-800 dark:bg-red-900/30 dark:text-red-300">
|
||||
<div className="m-2 rounded border-2 border-red-400 dark:border-red-700">
|
||||
<div className="overflow-auto whitespace-normal break-words bg-red-100 px-4 py-2 text-sm text-red-800 dark:bg-red-900/30 dark:text-red-300">
|
||||
{error}
|
||||
</div>
|
||||
</div>
|
||||
@@ -329,7 +329,7 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
|
||||
<>
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="no-scrollbar dark:bg-eerie-black relative block w-full bg-white p-4"
|
||||
className="no-scrollbar relative block w-full bg-white p-4 dark:bg-eerie-black"
|
||||
style={{
|
||||
overflow: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
@@ -345,7 +345,7 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
|
||||
>
|
||||
{isHovering && (
|
||||
<>
|
||||
<div className="absolute top-2 right-2 z-10 flex items-center gap-2 rounded-sm bg-black/70 px-2 py-1 text-xs text-white">
|
||||
<div className="absolute right-2 top-2 z-10 flex items-center gap-2 rounded bg-black/70 px-2 py-1 text-xs text-white">
|
||||
<button
|
||||
onClick={() =>
|
||||
setZoomFactor((prev) => Math.max(1, prev - 0.5))
|
||||
@@ -395,9 +395,9 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
|
||||
</div>
|
||||
|
||||
{showCode && (
|
||||
<div className="border-light-silver dark:border-raisin-black border-t">
|
||||
<div className="bg-platinum dark:bg-eerie-black-2 p-2">
|
||||
<span className="text-just-black dark:text-chinese-white text-xs font-medium">
|
||||
<div className="border-t border-light-silver dark:border-raisin-black">
|
||||
<div className="bg-platinum p-2 dark:bg-eerie-black-2">
|
||||
<span className="text-xs font-medium text-just-black dark:text-chinese-white">
|
||||
Mermaid Code
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -258,17 +258,17 @@ export default function MessageInput({
|
||||
};
|
||||
return (
|
||||
<div className="mx-2 flex w-full flex-col">
|
||||
<div className="border-dark-gray bg-lotion dark:border-grey relative flex w-full flex-col rounded-[23px] border dark:bg-transparent">
|
||||
<div className="flex flex-wrap gap-1.5 px-4 pt-3 pb-0 sm:gap-2 sm:px-6">
|
||||
<div className="relative flex w-full flex-col rounded-[23px] border border-dark-gray bg-lotion dark:border-grey dark:bg-transparent">
|
||||
<div className="flex flex-wrap gap-1.5 px-4 pb-0 pt-3 sm:gap-2 sm:px-6">
|
||||
{attachments.map((attachment, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className={`group dark:text-bright-gray relative flex items-center rounded-xl bg-[#EFF3F4] px-2 py-1 text-[12px] text-[#5D5D5D] sm:px-3 sm:py-1.5 sm:text-[14px] dark:bg-[#393B3D] ${
|
||||
className={`group relative flex items-center rounded-xl bg-[#EFF3F4] px-2 py-1 text-[12px] text-[#5D5D5D] dark:bg-[#393B3D] dark:text-bright-gray sm:px-3 sm:py-1.5 sm:text-[14px] ${
|
||||
attachment.status !== 'completed' ? 'opacity-70' : 'opacity-100'
|
||||
}`}
|
||||
title={attachment.fileName}
|
||||
>
|
||||
<div className="bg-purple-30 mr-2 items-center justify-center rounded-lg p-[5.5px]">
|
||||
<div className="mr-2 items-center justify-center rounded-lg bg-purple-30 p-[5.5px]">
|
||||
{attachment.status === 'completed' && (
|
||||
<img
|
||||
src={DocumentationDark}
|
||||
@@ -353,7 +353,7 @@ export default function MessageInput({
|
||||
onChange={handleChange}
|
||||
tabIndex={1}
|
||||
placeholder={t('inputPlaceholder')}
|
||||
className="inputbox-style no-scrollbar bg-lotion dark:text-bright-gray dark:placeholder:text-bright-gray/50 w-full overflow-x-hidden overflow-y-auto rounded-t-[23px] px-4 py-3 text-base leading-tight whitespace-pre-wrap opacity-100 placeholder:text-gray-500 focus:outline-hidden sm:px-6 sm:py-5 dark:bg-transparent"
|
||||
className="inputbox-style no-scrollbar w-full overflow-y-auto overflow-x-hidden whitespace-pre-wrap rounded-t-[23px] bg-lotion px-4 py-3 text-base leading-tight opacity-100 focus:outline-none dark:bg-transparent dark:text-bright-gray dark:placeholder-bright-gray dark:placeholder-opacity-50 sm:px-6 sm:py-5"
|
||||
onInput={handleInput}
|
||||
onKeyDown={handleKeyDown}
|
||||
aria-label={t('inputPlaceholder')}
|
||||
@@ -361,11 +361,11 @@ export default function MessageInput({
|
||||
</div>
|
||||
|
||||
<div className="flex items-center px-3 py-1.5 sm:px-4 sm:py-2">
|
||||
<div className="flex grow flex-wrap gap-1 sm:gap-2">
|
||||
<div className="flex flex-grow flex-wrap gap-1 sm:gap-2">
|
||||
{showSourceButton && (
|
||||
<button
|
||||
ref={sourceButtonRef}
|
||||
className="xs:px-3 xs:py-1.5 dark:border-purple-taupe flex max-w-[130px] items-center rounded-[32px] border border-[#AAAAAA] px-2 py-1 transition-colors hover:bg-gray-100 sm:max-w-[150px] dark:hover:bg-[#2C2E3C]"
|
||||
className="xs:px-3 xs:py-1.5 flex max-w-[130px] items-center rounded-[32px] border border-[#AAAAAA] px-2 py-1 transition-colors hover:bg-gray-100 dark:border-purple-taupe dark:hover:bg-[#2C2E3C] sm:max-w-[150px]"
|
||||
onClick={() => setIsSourcesPopupOpen(!isSourcesPopupOpen)}
|
||||
title={
|
||||
selectedDocs
|
||||
@@ -376,15 +376,15 @@ export default function MessageInput({
|
||||
<img
|
||||
src={SourceIcon}
|
||||
alt="Sources"
|
||||
className="mr-1 h-3.5 w-3.5 shrink-0 sm:mr-1.5 sm:h-4"
|
||||
className="mr-1 h-3.5 w-3.5 flex-shrink-0 sm:mr-1.5 sm:h-4"
|
||||
/>
|
||||
<span className="xs:text-[12px] dark:text-bright-gray truncate overflow-hidden text-[10px] font-medium text-[#5D5D5D] sm:text-[14px]">
|
||||
<span className="xs:text-[12px] overflow-hidden truncate text-[10px] font-medium text-[#5D5D5D] dark:text-bright-gray sm:text-[14px]">
|
||||
{selectedDocs
|
||||
? selectedDocs.name
|
||||
: t('conversation.sources.title')}
|
||||
</span>
|
||||
{!isTouch && (
|
||||
<span className="ml-1 hidden text-[10px] text-gray-500 sm:inline-block dark:text-gray-400">
|
||||
<span className="ml-1 hidden text-[10px] text-gray-500 dark:text-gray-400 sm:inline-block">
|
||||
{browserOS === 'mac' ? '(⌘K)' : '(ctrl+K)'}
|
||||
</span>
|
||||
)}
|
||||
@@ -394,26 +394,26 @@ export default function MessageInput({
|
||||
{showToolButton && (
|
||||
<button
|
||||
ref={toolButtonRef}
|
||||
className="xs:px-3 xs:py-1.5 xs:max-w-[150px] dark:border-purple-taupe flex max-w-[130px] items-center rounded-[32px] border border-[#AAAAAA] px-2 py-1 transition-colors hover:bg-gray-100 dark:hover:bg-[#2C2E3C]"
|
||||
className="xs:px-3 xs:py-1.5 xs:max-w-[150px] flex max-w-[130px] items-center rounded-[32px] border border-[#AAAAAA] px-2 py-1 transition-colors hover:bg-gray-100 dark:border-purple-taupe dark:hover:bg-[#2C2E3C]"
|
||||
onClick={() => setIsToolsPopupOpen(!isToolsPopupOpen)}
|
||||
>
|
||||
<img
|
||||
src={ToolIcon}
|
||||
alt="Tools"
|
||||
className="mr-1 h-3.5 w-3.5 shrink-0 sm:mr-1.5 sm:h-4 sm:w-4"
|
||||
className="mr-1 h-3.5 w-3.5 flex-shrink-0 sm:mr-1.5 sm:h-4 sm:w-4"
|
||||
/>
|
||||
<span className="xs:text-[12px] dark:text-bright-gray truncate overflow-hidden text-[10px] font-medium text-[#5D5D5D] sm:text-[14px]">
|
||||
<span className="xs:text-[12px] overflow-hidden truncate text-[10px] font-medium text-[#5D5D5D] dark:text-bright-gray sm:text-[14px]">
|
||||
{t('settings.tools.label')}
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
<label className="xs:px-3 xs:py-1.5 dark:border-purple-taupe flex cursor-pointer items-center rounded-[32px] border border-[#AAAAAA] px-2 py-1 transition-colors hover:bg-gray-100 dark:hover:bg-[#2C2E3C]">
|
||||
<label className="xs:px-3 xs:py-1.5 flex cursor-pointer items-center rounded-[32px] border border-[#AAAAAA] px-2 py-1 transition-colors hover:bg-gray-100 dark:border-purple-taupe dark:hover:bg-[#2C2E3C]">
|
||||
<img
|
||||
src={ClipIcon}
|
||||
alt="Attach"
|
||||
className="mr-1 h-3.5 w-3.5 sm:mr-1.5 sm:h-4 sm:w-4"
|
||||
/>
|
||||
<span className="xs:text-[12px] dark:text-bright-gray text-[10px] font-medium text-[#5D5D5D] sm:text-[14px]">
|
||||
<span className="xs:text-[12px] text-[10px] font-medium text-[#5D5D5D] dark:text-bright-gray sm:text-[14px]">
|
||||
{t('conversation.attachments.attach')}
|
||||
</span>
|
||||
<input
|
||||
@@ -428,7 +428,7 @@ export default function MessageInput({
|
||||
<button
|
||||
onClick={loading ? undefined : handleSubmit}
|
||||
aria-label={loading ? t('loading') : t('send')}
|
||||
className={`flex items-center justify-center rounded-full p-2 sm:p-2.5 ${loading ? 'bg-gray-300 dark:bg-gray-600' : 'bg-black dark:bg-white'} ml-auto shrink-0`}
|
||||
className={`flex items-center justify-center rounded-full p-2 sm:p-2.5 ${loading ? 'bg-gray-300 dark:bg-gray-600' : 'bg-black dark:bg-white'} ml-auto flex-shrink-0`}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? (
|
||||
|
||||
@@ -17,7 +17,7 @@ export type OptionType = {
|
||||
type MultiSelectPopupProps = {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
anchorRef: React.RefObject<HTMLElement | null>;
|
||||
anchorRef: React.RefObject<HTMLElement>;
|
||||
options: OptionType[];
|
||||
selectedIds: Set<string | number>;
|
||||
onSelectionChange: (newSelectedIds: Set<string | number>) => void;
|
||||
@@ -79,18 +79,18 @@ export default function MultiSelectPopup({
|
||||
<img
|
||||
src={icon}
|
||||
alt=""
|
||||
className="mr-3 h-5 w-5 shrink-0"
|
||||
className="mr-3 h-5 w-5 flex-shrink-0"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<span className="mr-3 h-5 w-5 shrink-0" aria-hidden="true">
|
||||
<span className="mr-3 h-5 w-5 flex-shrink-0" aria-hidden="true">
|
||||
{icon}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
return <span className="mr-3 shrink-0">{icon}</span>;
|
||||
return <span className="mr-3 flex-shrink-0">{icon}</span>;
|
||||
};
|
||||
|
||||
useLayoutEffect(() => {
|
||||
@@ -168,7 +168,7 @@ export default function MultiSelectPopup({
|
||||
return (
|
||||
<div
|
||||
ref={popupRef}
|
||||
className="border-light-silver bg-lotion dark:border-dim-gray dark:bg-charleston-green-2 fixed z-9999 flex flex-col rounded-lg border shadow-[0px_9px_46px_8px_#0000001F,0px_24px_38px_3px_#00000024,0px_11px_15px_-7px_#00000033]"
|
||||
className="fixed z-[9999] flex flex-col rounded-lg border border-light-silver bg-lotion shadow-[0px_9px_46px_8px_#0000001F,0px_24px_38px_3px_#00000024,0px_11px_15px_-7px_#00000033] dark:border-dim-gray dark:bg-charleston-green-2"
|
||||
style={{
|
||||
top: popupPosition.showAbove ? undefined : popupPosition.top,
|
||||
bottom: popupPosition.showAbove
|
||||
@@ -181,7 +181,7 @@ export default function MultiSelectPopup({
|
||||
}}
|
||||
>
|
||||
{(title || showSearch) && (
|
||||
<div className="shrink-0 p-4">
|
||||
<div className="flex-shrink-0 p-4">
|
||||
{title && (
|
||||
<h3 className="mb-4 text-lg font-medium text-gray-900 dark:text-white">
|
||||
{title}
|
||||
@@ -206,13 +206,13 @@ export default function MultiSelectPopup({
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div className="dark:border-dim-gray mx-4 mb-4 grow overflow-auto rounded-md border border-[#D9D9D9]">
|
||||
<div className="mx-4 mb-4 flex-grow overflow-auto rounded-md border border-[#D9D9D9] dark:border-dim-gray">
|
||||
{loading ? (
|
||||
<div className="flex h-full items-center justify-center py-4">
|
||||
<div className="h-6 w-6 animate-spin rounded-full border-b-2 border-gray-900 dark:border-white"></div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="h-full overflow-y-auto [&::-webkit-scrollbar]:w-2 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-gray-400 dark:[&::-webkit-scrollbar-thumb]:bg-gray-600 [&::-webkit-scrollbar-track]:bg-gray-200 dark:[&::-webkit-scrollbar-track]:bg-[#2C2E3C]">
|
||||
<div className="h-full overflow-y-auto [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-gray-400 dark:[&::-webkit-scrollbar-thumb]:bg-gray-600 [&::-webkit-scrollbar-track]:bg-gray-200 dark:[&::-webkit-scrollbar-track]:bg-[#2C2E3C] [&::-webkit-scrollbar]:w-2">
|
||||
{filteredOptions.length === 0 ? (
|
||||
<div className="flex h-full flex-col items-center justify-center px-4 py-8 text-center">
|
||||
<img
|
||||
@@ -233,22 +233,22 @@ export default function MultiSelectPopup({
|
||||
<div
|
||||
key={option.id}
|
||||
onClick={() => handleOptionClick(option.id)}
|
||||
className="dark:border-dim-gray dark:hover:bg-charleston-green-3 flex cursor-pointer items-center justify-between border-b border-[#D9D9D9] p-3 last:border-b-0 hover:bg-gray-100"
|
||||
className="flex cursor-pointer items-center justify-between border-b border-[#D9D9D9] p-3 last:border-b-0 hover:bg-gray-100 dark:border-dim-gray dark:hover:bg-charleston-green-3"
|
||||
role="option"
|
||||
aria-selected={isSelected}
|
||||
>
|
||||
<div className="mr-3 flex grow items-center overflow-hidden">
|
||||
<div className="mr-3 flex flex-grow items-center overflow-hidden">
|
||||
{option.icon && renderIcon(option.icon)}
|
||||
<p
|
||||
className="overflow-hidden text-sm font-medium text-ellipsis whitespace-nowrap text-gray-900 dark:text-white"
|
||||
className="overflow-hidden overflow-ellipsis whitespace-nowrap text-sm font-medium text-gray-900 dark:text-white"
|
||||
title={option.label}
|
||||
>
|
||||
{option.label}
|
||||
</p>
|
||||
</div>
|
||||
<div className="shrink-0">
|
||||
<div className="flex-shrink-0">
|
||||
<div
|
||||
className={`dark:bg-charleston-green-2 flex h-4 w-4 items-center justify-center rounded-xs border border-[#C6C6C6] bg-white dark:border-[#757783]`}
|
||||
className={`flex h-4 w-4 items-center justify-center rounded-sm border border-[#C6C6C6] bg-white dark:border-[#757783] dark:bg-charleston-green-2`}
|
||||
aria-hidden="true"
|
||||
>
|
||||
{isSelected && (
|
||||
@@ -269,7 +269,7 @@ export default function MultiSelectPopup({
|
||||
)}
|
||||
</div>
|
||||
{footerContent && (
|
||||
<div className="border-light-silver dark:border-dim-gray shrink-0 border-t p-4">
|
||||
<div className="flex-shrink-0 border-t border-light-silver p-4 dark:border-dim-gray">
|
||||
{footerContent}
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -50,10 +50,10 @@ const SettingsBar = ({ setActiveTab, activeTab }: SettingsBarProps) => {
|
||||
return (
|
||||
<div className="relative mt-6 flex flex-row items-center space-x-1 overflow-auto md:space-x-0">
|
||||
<div
|
||||
className={`${hiddenGradient === 'left' ? 'hidden' : ''} dark:from-raisin-black pointer-events-none absolute inset-y-0 left-6 w-14 bg-linear-to-r from-white md:hidden`}
|
||||
className={`${hiddenGradient === 'left' ? 'hidden' : ''} pointer-events-none absolute inset-y-0 left-6 w-14 bg-gradient-to-r from-white dark:from-raisin-black md:hidden`}
|
||||
></div>
|
||||
<div
|
||||
className={`${hiddenGradient === 'right' ? 'hidden' : ''} dark:from-raisin-black pointer-events-none absolute inset-y-0 right-6 w-14 bg-linear-to-l from-white md:hidden`}
|
||||
className={`${hiddenGradient === 'right' ? 'hidden' : ''} pointer-events-none absolute inset-y-0 right-6 w-14 bg-gradient-to-l from-white dark:from-raisin-black md:hidden`}
|
||||
></div>
|
||||
|
||||
<div className="z-10 md:hidden">
|
||||
@@ -77,7 +77,7 @@ const SettingsBar = ({ setActiveTab, activeTab }: SettingsBarProps) => {
|
||||
onClick={() => setActiveTab(tab)}
|
||||
className={`h-9 snap-start rounded-3xl px-4 font-bold transition-colors ${
|
||||
activeTab === tab
|
||||
? 'dark:bg-dark-charcoal bg-[#F4F4F5] text-neutral-900 dark:text-white'
|
||||
? 'bg-[#F4F4F5] text-neutral-900 dark:bg-dark-charcoal dark:text-white'
|
||||
: 'text-neutral-700 hover:text-neutral-900 dark:text-neutral-300 dark:hover:text-white'
|
||||
}`}
|
||||
role="tab"
|
||||
|
||||
@@ -33,13 +33,13 @@ export default function Sidebar({
|
||||
return (
|
||||
<div ref={sidebarRef} className="h-vh relative">
|
||||
<div
|
||||
className={`dark:bg-chinese-black fixed top-0 right-0 z-50 h-full w-72 transform bg-white shadow-xl transition-all duration-300 sm:w-96 ${
|
||||
className={`fixed right-0 top-0 z-50 h-full w-72 transform bg-white shadow-xl transition-all duration-300 dark:bg-chinese-black sm:w-96 ${
|
||||
isOpen ? 'translate-x-[10px]' : 'translate-x-full'
|
||||
} border-l border-[#9ca3af]/10`}
|
||||
>
|
||||
<div className="flex w-full flex-row items-end justify-end px-4 pt-3">
|
||||
<button
|
||||
className="hover:bg-gray-1000 dark:hover:bg-gun-metal w-7 rounded-full p-2"
|
||||
className="w-7 rounded-full p-2 hover:bg-gray-1000 hover:dark:bg-gun-metal"
|
||||
onClick={() => toggleState(!isOpen)}
|
||||
>
|
||||
<img className="filter dark:invert" src={Exit} />
|
||||
|
||||
@@ -43,16 +43,16 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
|
||||
{[...Array(4)].map((_, idx) => (
|
||||
<tr key={idx} className="animate-pulse">
|
||||
<td className="w-[45%] px-4 py-4">
|
||||
<div className="h-4 w-full rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
</td>
|
||||
<td className="w-[20%] px-4 py-4">
|
||||
<div className="h-4 w-full rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
</td>
|
||||
<td className="w-[25%] px-4 py-4">
|
||||
<div className="h-4 w-full rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
</td>
|
||||
<td className="w-[10%] px-4 py-4">
|
||||
<div className="h-4 w-full rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
@@ -64,16 +64,16 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
|
||||
{[...Array(4)].map((_, idx) => (
|
||||
<tr key={idx} className="animate-pulse">
|
||||
<td className="p-2">
|
||||
<div className="mx-auto h-4 w-3/4 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mx-auto h-4 w-3/4 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
</td>
|
||||
<td className="p-2">
|
||||
<div className="mx-auto h-4 w-full rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mx-auto h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
</td>
|
||||
<td className="p-2">
|
||||
<div className="mx-auto h-4 w-full rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mx-auto h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
</td>
|
||||
<td className="p-2">
|
||||
<div className="mx-auto h-4 w-8 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mx-auto h-4 w-8 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
@@ -82,10 +82,10 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
|
||||
|
||||
const renderDropdown = () => (
|
||||
<div className="animate-pulse">
|
||||
<div className="mb-2 h-4 w-24 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-24 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="flex h-14 w-[360px] items-center justify-between rounded-3xl bg-gray-300 px-4 dark:bg-gray-600">
|
||||
<div className="h-3 w-24 rounded-sm bg-gray-400 dark:bg-gray-700"></div>
|
||||
<div className="h-3 w-3 rounded-sm bg-gray-400 dark:bg-gray-700"></div>
|
||||
<div className="h-3 w-24 rounded bg-gray-400 dark:bg-gray-700"></div>
|
||||
<div className="h-3 w-3 rounded bg-gray-400 dark:bg-gray-700"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -95,14 +95,14 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
|
||||
{[...Array(8)].map((_, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="dark:hover:bg-dark-charcoal flex w-full items-start p-2 hover:bg-[#F9F9F9]"
|
||||
className="flex w-full items-start p-2 hover:bg-[#F9F9F9] hover:dark:bg-dark-charcoal"
|
||||
>
|
||||
<div className="flex w-full items-center gap-2">
|
||||
<div className="h-3 w-3 rounded-lg bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="flex w-full flex-row items-center gap-2">
|
||||
<div className="h-3 w-[30%] rounded-lg bg-gray-300 lg:w-52 dark:bg-gray-600"></div>
|
||||
<div className="h-3 w-[16%] rounded-lg bg-gray-300 lg:w-28 dark:bg-gray-600"></div>
|
||||
<div className="h-3 w-[40%] rounded-lg bg-gray-300 lg:w-64 dark:bg-gray-600"></div>
|
||||
<div className="h-3 w-[30%] rounded-lg bg-gray-300 dark:bg-gray-600 lg:w-52"></div>
|
||||
<div className="h-3 w-[16%] rounded-lg bg-gray-300 dark:bg-gray-600 lg:w-28"></div>
|
||||
<div className="h-3 w-[40%] rounded-lg bg-gray-300 dark:bg-gray-600 lg:w-64"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -117,32 +117,32 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
|
||||
key={idx}
|
||||
className={`p-6 ${
|
||||
skeletonCount === 1 ? 'w-full' : 'w-60'
|
||||
} dark:bg-raisin-black animate-pulse rounded-3xl`}
|
||||
} animate-pulse rounded-3xl dark:bg-raisin-black`}
|
||||
>
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<div className="mb-2 h-4 w-3/4 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-5/6 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-1/2 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-3/4 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-full rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-3/4 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-5/6 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-1/2 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-3/4 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
</div>
|
||||
<div className="my-4 border-t border-gray-400 dark:border-gray-700"></div>
|
||||
<div>
|
||||
<div className="mb-2 h-4 w-2/3 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-1/4 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-full rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-2/3 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-1/4 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
</div>
|
||||
<div className="my-4 border-t border-gray-400 dark:border-gray-700"></div>
|
||||
<div>
|
||||
<div className="mb-2 h-4 w-5/6 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-1/3 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-2/3 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-full rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-5/6 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-1/3 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-2/3 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
</div>
|
||||
<div className="my-4 border-t border-gray-400 dark:border-gray-700"></div>
|
||||
<div className="mb-2 h-4 w-3/4 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-5/6 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-3/4 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-2 h-4 w-5/6 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -154,27 +154,27 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
|
||||
{[...Array(skeletonCount)].map((_, idx) => (
|
||||
<div
|
||||
key={idx}
|
||||
className="dark:bg-raisin-black w-full animate-pulse rounded-3xl p-6"
|
||||
className="w-full animate-pulse rounded-3xl p-6 dark:bg-raisin-black"
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<div className="space-y-2">
|
||||
<div className="mb-4 h-4 w-1/3 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-4 h-4 w-1/3 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="grid grid-cols-6 items-end gap-2">
|
||||
<div className="h-32 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-24 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-40 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-28 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-36 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-20 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-32 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-24 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-40 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-28 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-36 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-20 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<div className="mb-4 h-4 w-1/4 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-32 rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="mb-4 h-4 w-1/4 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-32 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="h-4 w-full rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-4 w-full rounded-sm bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -79,13 +79,13 @@ function SourceDropdown({
|
||||
<div className="relative w-5/6 rounded-3xl" ref={dropdownRef}>
|
||||
<button
|
||||
onClick={() => setIsDocsListOpen(!isDocsListOpen)}
|
||||
className={`border-silver flex w-full cursor-pointer items-center border bg-white p-[11px] dark:bg-transparent ${
|
||||
className={`flex w-full cursor-pointer items-center border border-silver bg-white p-[11px] dark:bg-transparent ${
|
||||
isDocsListOpen
|
||||
? 'dark:border-silver/40 rounded-t-3xl'
|
||||
: 'dark:border-purple-taupe rounded-3xl'
|
||||
? 'rounded-t-3xl dark:border-silver/40'
|
||||
: 'rounded-3xl dark:border-purple-taupe'
|
||||
}`}
|
||||
>
|
||||
<span className="dark:text-bright-gray mr-2 ml-1 flex-1 overflow-hidden text-left text-ellipsis">
|
||||
<span className="ml-1 mr-2 flex-1 overflow-hidden text-ellipsis text-left dark:text-bright-gray">
|
||||
<div className="flex flex-row gap-2">
|
||||
<p className="max-w-3/4 truncate whitespace-nowrap">
|
||||
{selectedDocs?.name || 'None'}
|
||||
@@ -101,14 +101,14 @@ function SourceDropdown({
|
||||
/>
|
||||
</button>
|
||||
{isDocsListOpen && (
|
||||
<div className="border-silver dark:border-silver/40 dark:bg-dark-charcoal absolute right-0 left-0 z-20 -mt-1 max-h-28 overflow-y-auto rounded-b-xl border bg-white shadow-lg">
|
||||
<div className="absolute left-0 right-0 z-20 -mt-1 max-h-28 overflow-y-auto rounded-b-xl border border-silver bg-white shadow-lg dark:border-silver/40 dark:bg-dark-charcoal">
|
||||
{options ? (
|
||||
options.map((option: any, index: number) => {
|
||||
if (option.model === embeddingsName) {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="dark:text-bright-gray flex cursor-pointer items-center justify-between hover:bg-gray-100 dark:hover:bg-[#545561]"
|
||||
className="flex cursor-pointer items-center justify-between hover:bg-gray-100 dark:text-bright-gray dark:hover:bg-[#545561]"
|
||||
onClick={() => {
|
||||
dispatch(setSelectedDocs(option));
|
||||
setIsDocsListOpen(false);
|
||||
@@ -119,7 +119,7 @@ function SourceDropdown({
|
||||
onClick={() => {
|
||||
setIsDocsListOpen(false);
|
||||
}}
|
||||
className="ml-4 flex-1 overflow-hidden py-3 text-ellipsis whitespace-nowrap"
|
||||
className="ml-4 flex-1 overflow-hidden overflow-ellipsis whitespace-nowrap py-3"
|
||||
>
|
||||
{option.name}
|
||||
</span>
|
||||
@@ -143,11 +143,11 @@ function SourceDropdown({
|
||||
<></>
|
||||
)}
|
||||
<div
|
||||
className="dark:text-bright-gray dark:hover:bg-purple-taupe flex cursor-pointer items-center justify-between hover:bg-gray-100"
|
||||
className="flex cursor-pointer items-center justify-between hover:bg-gray-100 dark:text-bright-gray dark:hover:bg-purple-taupe"
|
||||
onClick={handleEmptyDocumentSelect}
|
||||
>
|
||||
<span
|
||||
className="ml-4 flex-1 overflow-hidden py-3 text-ellipsis whitespace-nowrap"
|
||||
className="ml-4 flex-1 overflow-hidden overflow-ellipsis whitespace-nowrap py-3"
|
||||
onClick={() => {
|
||||
handlePostDocumentSelect(null);
|
||||
}}
|
||||
|
||||
@@ -16,7 +16,7 @@ import { ActiveState } from '../models/misc';
|
||||
type SourcesPopupProps = {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
anchorRef: React.RefObject<HTMLButtonElement | null>;
|
||||
anchorRef: React.RefObject<HTMLButtonElement>;
|
||||
handlePostDocumentSelect: (doc: Doc | null) => void;
|
||||
setUploadModalState: React.Dispatch<React.SetStateAction<ActiveState>>;
|
||||
};
|
||||
@@ -110,7 +110,7 @@ export default function SourcesPopup({
|
||||
return (
|
||||
<div
|
||||
ref={popupRef}
|
||||
className="bg-lotion dark:bg-charleston-green-2 fixed z-50 flex flex-col rounded-xl shadow-[0px_9px_46px_8px_#0000001F,0px_24px_38px_3px_#00000024,0px_11px_15px_-7px_#00000033]"
|
||||
className="fixed z-50 flex flex-col rounded-xl bg-lotion shadow-[0px_9px_46px_8px_#0000001F,0px_24px_38px_3px_#00000024,0px_11px_15px_-7px_#00000033] dark:bg-charleston-green-2"
|
||||
style={{
|
||||
top: popupPosition.showAbove ? popupPosition.top : undefined,
|
||||
bottom: popupPosition.showAbove
|
||||
@@ -124,8 +124,8 @@ export default function SourcesPopup({
|
||||
}}
|
||||
>
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="shrink-0 px-4 py-4 md:px-6">
|
||||
<h2 className="dark:text-bright-gray mb-4 text-lg font-bold text-[#141414] dark:text-[20px]">
|
||||
<div className="flex-shrink-0 px-4 py-4 md:px-6">
|
||||
<h2 className="mb-4 text-lg font-bold text-[#141414] dark:text-[20px] dark:text-bright-gray">
|
||||
{t('conversation.sources.text')}
|
||||
</h2>
|
||||
|
||||
@@ -142,7 +142,7 @@ export default function SourcesPopup({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="dark:border-dim-gray mx-4 grow overflow-y-auto rounded-md border border-[#D9D9D9] [&::-webkit-scrollbar-thumb]:bg-[#888] [&::-webkit-scrollbar-thumb]:hover:bg-[#555] [&::-webkit-scrollbar-track]:bg-[#E2E8F0] dark:[&::-webkit-scrollbar-track]:bg-[#2C2E3C]">
|
||||
<div className="mx-4 flex-grow overflow-y-auto rounded-md border border-[#D9D9D9] dark:border-dim-gray [&::-webkit-scrollbar-thumb]:bg-[#888] [&::-webkit-scrollbar-thumb]:hover:bg-[#555] [&::-webkit-scrollbar-track]:bg-[#E2E8F0] dark:[&::-webkit-scrollbar-track]:bg-[#2C2E3C]">
|
||||
{options ? (
|
||||
<>
|
||||
{filteredOptions?.map((option: any, index: number) => {
|
||||
@@ -156,7 +156,7 @@ export default function SourcesPopup({
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="border-opacity-80 dark:border-dim-gray flex cursor-pointer items-center border-b border-[#D9D9D9] p-3 transition-colors hover:bg-gray-100 dark:text-[14px] dark:hover:bg-[#2C2E3C]"
|
||||
className="flex cursor-pointer items-center border-b border-[#D9D9D9] border-opacity-80 p-3 transition-colors hover:bg-gray-100 dark:border-dim-gray dark:text-[14px] dark:hover:bg-[#2C2E3C]"
|
||||
onClick={() => {
|
||||
if (isSelected) {
|
||||
dispatch(setSelectedDocs(null));
|
||||
@@ -172,13 +172,13 @@ export default function SourcesPopup({
|
||||
alt="Source"
|
||||
width={14}
|
||||
height={14}
|
||||
className="mr-3 shrink-0"
|
||||
className="mr-3 flex-shrink-0"
|
||||
/>
|
||||
<span className="dark:text-bright-gray mr-3 grow overflow-hidden font-medium text-ellipsis whitespace-nowrap text-[#5D5D5D]">
|
||||
<span className="mr-3 flex-grow overflow-hidden overflow-ellipsis whitespace-nowrap font-medium text-[#5D5D5D] dark:text-bright-gray">
|
||||
{option.name}
|
||||
</span>
|
||||
<div
|
||||
className={`flex h-4 w-4 shrink-0 items-center justify-center border border-[#C6C6C6] p-[0.5px] dark:border-[#757783]`}
|
||||
className={`flex h-4 w-4 flex-shrink-0 items-center justify-center border border-[#C6C6C6] p-[0.5px] dark:border-[#757783]`}
|
||||
>
|
||||
{isSelected && (
|
||||
<img
|
||||
@@ -195,16 +195,16 @@ export default function SourcesPopup({
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
<div className="dark:text-bright-gray p-4 text-center text-gray-500 dark:text-[14px]">
|
||||
<div className="p-4 text-center text-gray-500 dark:text-[14px] dark:text-bright-gray">
|
||||
{t('noSourcesAvailable')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="shrink-0 px-4 py-4 opacity-75 transition-opacity duration-200 hover:opacity-100 md:px-6">
|
||||
<div className="flex-shrink-0 px-4 py-4 opacity-75 transition-opacity duration-200 hover:opacity-100 md:px-6">
|
||||
<a
|
||||
href="/settings/documents"
|
||||
className="text-violets-are-blue inline-flex items-center gap-2 text-base font-medium"
|
||||
className="inline-flex items-center gap-2 text-base font-medium text-violets-are-blue"
|
||||
onClick={onClose}
|
||||
>
|
||||
{t('settings.documents.goToDocuments')}
|
||||
@@ -212,10 +212,10 @@ export default function SourcesPopup({
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div className="flex shrink-0 justify-start px-4 py-3 md:px-6">
|
||||
<div className="flex flex-shrink-0 justify-start px-4 py-3 md:px-6">
|
||||
<button
|
||||
onClick={handleUploadClick}
|
||||
className="border-violets-are-blue text-violets-are-blue hover:bg-violets-are-blue w-auto rounded-full border px-4 py-2 text-[14px] font-medium transition-colors duration-200 hover:text-white"
|
||||
className="w-auto rounded-full border border-violets-are-blue px-4 py-2 text-[14px] font-medium text-violets-are-blue transition-colors duration-200 hover:bg-violets-are-blue hover:text-white"
|
||||
>
|
||||
{t('settings.documents.uploadNew')}
|
||||
</button>
|
||||
|
||||
@@ -14,7 +14,7 @@ import { useDarkTheme } from '../hooks';
|
||||
interface ToolsPopupProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
anchorRef: React.RefObject<HTMLButtonElement | null>;
|
||||
anchorRef: React.RefObject<HTMLButtonElement>;
|
||||
}
|
||||
|
||||
export default function ToolsPopup({
|
||||
@@ -136,7 +136,7 @@ export default function ToolsPopup({
|
||||
return (
|
||||
<div
|
||||
ref={popupRef}
|
||||
className="border-light-silver bg-lotion dark:border-dim-gray dark:bg-charleston-green-2 fixed z-9999 rounded-lg border shadow-[0px_9px_46px_8px_#0000001F,0px_24px_38px_3px_#00000024,0px_11px_15px_-7px_#00000033]"
|
||||
className="fixed z-[9999] rounded-lg border border-light-silver bg-lotion shadow-[0px_9px_46px_8px_#0000001F,0px_24px_38px_3px_#00000024,0px_11px_15px_-7px_#00000033] dark:border-dim-gray dark:bg-charleston-green-2"
|
||||
style={{
|
||||
top: popupPosition.showAbove ? popupPosition.top : undefined,
|
||||
bottom: popupPosition.showAbove
|
||||
@@ -150,7 +150,7 @@ export default function ToolsPopup({
|
||||
}}
|
||||
>
|
||||
<div className="flex h-full flex-col">
|
||||
<div className="shrink-0 p-4">
|
||||
<div className="flex-shrink-0 p-4">
|
||||
<h3 className="mb-4 text-lg font-medium text-gray-900 dark:text-white">
|
||||
{t('settings.tools.label')}
|
||||
</h3>
|
||||
@@ -169,11 +169,11 @@ export default function ToolsPopup({
|
||||
</div>
|
||||
|
||||
{loading ? (
|
||||
<div className="flex grow justify-center py-4">
|
||||
<div className="flex flex-grow justify-center py-4">
|
||||
<div className="h-6 w-6 animate-spin rounded-full border-b-2 border-gray-900 dark:border-white"></div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="dark:border-dim-gray mx-4 grow overflow-hidden rounded-md border border-[#D9D9D9]">
|
||||
<div className="mx-4 flex-grow overflow-hidden rounded-md border border-[#D9D9D9] dark:border-dim-gray">
|
||||
<div className="h-full overflow-y-auto [&::-webkit-scrollbar-thumb]:bg-[#888] [&::-webkit-scrollbar-thumb]:hover:bg-[#555] [&::-webkit-scrollbar-track]:bg-[#E2E8F0] dark:[&::-webkit-scrollbar-track]:bg-[#2C2E3C]">
|
||||
{filteredTools.length === 0 ? (
|
||||
<div className="flex h-full flex-col items-center justify-center py-8">
|
||||
@@ -191,21 +191,21 @@ export default function ToolsPopup({
|
||||
<div
|
||||
key={tool.id}
|
||||
onClick={() => updateToolStatus(tool.id, !tool.status)}
|
||||
className="dark:border-dim-gray dark:hover:bg-charleston-green-3 flex items-center justify-between border-b border-[#D9D9D9] p-3 hover:bg-gray-100"
|
||||
className="flex items-center justify-between border-b border-[#D9D9D9] p-3 hover:bg-gray-100 dark:border-dim-gray dark:hover:bg-charleston-green-3"
|
||||
>
|
||||
<div className="mr-3 flex grow items-center">
|
||||
<div className="mr-3 flex flex-grow items-center">
|
||||
<img
|
||||
src={`/toolIcons/tool_${tool.name}.svg`}
|
||||
alt={`${tool.displayName} icon`}
|
||||
className="mr-4 h-5 w-5 shrink-0"
|
||||
className="mr-4 h-5 w-5 flex-shrink-0"
|
||||
/>
|
||||
<div className="overflow-hidden">
|
||||
<p className="overflow-hidden text-xs font-medium text-ellipsis whitespace-nowrap text-gray-900 dark:text-white">
|
||||
<p className="overflow-hidden overflow-ellipsis whitespace-nowrap text-xs font-medium text-gray-900 dark:text-white">
|
||||
{tool.customName || tool.displayName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex shrink-0 items-center">
|
||||
<div className="flex flex-shrink-0 items-center">
|
||||
<div
|
||||
className={`flex h-4 w-4 items-center justify-center border border-[#C6C6C6] p-[0.5px] dark:border-[#757783]`}
|
||||
>
|
||||
@@ -226,10 +226,10 @@ export default function ToolsPopup({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="shrink-0 p-4 opacity-75 transition-opacity duration-200 hover:opacity-100">
|
||||
<div className="flex-shrink-0 p-4 opacity-75 transition-opacity duration-200 hover:opacity-100">
|
||||
<a
|
||||
href="/settings/tools"
|
||||
className="text-purple-30 inline-flex items-center text-base font-medium"
|
||||
className="inline-flex items-center text-base font-medium text-purple-30"
|
||||
>
|
||||
{t('settings.tools.manageTools')}
|
||||
<img
|
||||
|
||||
@@ -220,7 +220,7 @@ export default function Conversation() {
|
||||
}
|
||||
/>
|
||||
|
||||
<div className="bg-opacity-0 z-3 flex h-auto w-full max-w-[1300px] flex-col items-end self-center rounded-2xl py-1 md:w-9/12 lg:w-8/12 xl:w-8/12 2xl:w-6/12">
|
||||
<div className="z-3 flex h-auto w-full max-w-[1300px] flex-col items-end self-center rounded-2xl bg-opacity-0 py-1 md:w-9/12 lg:w-8/12 xl:w-8/12 2xl:w-6/12">
|
||||
<div
|
||||
{...getRootProps()}
|
||||
className="flex w-full items-center rounded-[40px]"
|
||||
@@ -239,17 +239,17 @@ export default function Conversation() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<p className="text-gray-4000 dark:text-sonic-silver hidden w-screen self-center bg-transparent py-2 text-center text-xs md:inline md:w-full">
|
||||
<p className="hidden w-[100vw] self-center bg-transparent py-2 text-center text-xs text-gray-4000 dark:text-sonic-silver md:inline md:w-full">
|
||||
{t('tagline')}
|
||||
</p>
|
||||
</div>
|
||||
{handleDragActive && (
|
||||
<div className="bg-opacity-50 dark:bg-gray-alpha pointer-events-none fixed top-0 left-0 z-30 flex size-full flex-col items-center justify-center bg-white">
|
||||
<div className="pointer-events-none fixed left-0 top-0 z-30 flex size-full flex-col items-center justify-center bg-white bg-opacity-50 dark:bg-gray-alpha">
|
||||
<img className="filter dark:invert" src={DragFileUpload} />
|
||||
<span className="text-outer-space dark:text-silver px-2 text-2xl font-bold">
|
||||
<span className="px-2 text-2xl font-bold text-outer-space dark:text-silver">
|
||||
{t('modals.uploadDoc.drag.title')}
|
||||
</span>
|
||||
<span className="text-s text-outer-space dark:text-silver w-48 p-2 text-center">
|
||||
<span className="text-s w-48 p-2 text-center text-outer-space dark:text-silver">
|
||||
{t('modals.uploadDoc.drag.description')}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -96,7 +96,7 @@ const ConversationBubble = forwardRef<
|
||||
const [isDislikeClicked, setIsDislikeClicked] = useState(false);
|
||||
const [activeTooltip, setActiveTooltip] = useState<number | null>(null);
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState<boolean>(false);
|
||||
const editableQueryRef = useRef<HTMLDivElement>(null);
|
||||
const editableQueryRef = useRef<HTMLDivElement | null>(null);
|
||||
const [isQuestionCollapsed, setIsQuestionCollapsed] = useState(true);
|
||||
|
||||
useOutsideAlerter(editableQueryRef, () => setIsEditClicked(false), [], true);
|
||||
@@ -122,14 +122,14 @@ const ConversationBubble = forwardRef<
|
||||
>
|
||||
<div className="flex flex-col items-end">
|
||||
{filesAttached && filesAttached.length > 0 && (
|
||||
<div className="mr-12 mb-4 flex flex-wrap justify-end gap-2">
|
||||
<div className="mb-4 mr-12 flex flex-wrap justify-end gap-2">
|
||||
{filesAttached.map((file, index) => (
|
||||
<div
|
||||
key={index}
|
||||
title={file.fileName}
|
||||
className="dark:text-bright-gray flex items-center rounded-xl bg-[#EFF3F4] p-2 text-[14px] text-[#5D5D5D] dark:bg-[#393B3D]"
|
||||
className="flex items-center rounded-xl bg-[#EFF3F4] p-2 text-[14px] text-[#5D5D5D] dark:bg-[#393B3D] dark:text-bright-gray"
|
||||
>
|
||||
<div className="bg-purple-30 mr-2 items-center justify-center rounded-lg p-[5.5px]">
|
||||
<div className="mr-2 items-center justify-center rounded-lg bg-purple-30 p-[5.5px]">
|
||||
<img
|
||||
src={DocumentationDark}
|
||||
alt="Attachment"
|
||||
@@ -149,7 +149,7 @@ const ConversationBubble = forwardRef<
|
||||
>
|
||||
<Avatar
|
||||
size="SMALL"
|
||||
className="mt-2 shrink-0 text-2xl"
|
||||
className="mt-2 flex-shrink-0 text-2xl"
|
||||
avatar={
|
||||
<img className="mr-1 rounded-full" width={30} src={UserIcon} />
|
||||
}
|
||||
@@ -157,7 +157,12 @@ const ConversationBubble = forwardRef<
|
||||
{!isEditClicked && (
|
||||
<>
|
||||
<div className="relative mr-2 flex w-full flex-col">
|
||||
<div className="from-medium-purple to-slate-blue mr-2 ml-2 flex max-w-full items-start gap-2 rounded-[28px] bg-linear-to-b px-5 py-4 text-sm leading-normal break-words whitespace-pre-wrap text-white sm:text-base">
|
||||
<div
|
||||
style={{
|
||||
wordBreak: 'break-word',
|
||||
}}
|
||||
className="ml-2 mr-2 flex max-w-full items-start gap-2 whitespace-pre-wrap rounded-[28px] bg-gradient-to-b from-medium-purple to-slate-blue py-[14px] pl-[19px] pr-3 text-sm leading-normal text-white sm:text-base"
|
||||
>
|
||||
<div
|
||||
ref={messageRef}
|
||||
className={`${isQuestionCollapsed ? 'line-clamp-4' : ''} w-full`}
|
||||
@@ -170,7 +175,7 @@ const ConversationBubble = forwardRef<
|
||||
e.stopPropagation();
|
||||
setIsQuestionCollapsed(!isQuestionCollapsed);
|
||||
}}
|
||||
className="ml-1 rounded-full p-2 hover:bg-[#D9D9D933]"
|
||||
className="rounded-full p-2.5 hover:bg-[#D9D9D933]"
|
||||
>
|
||||
<img
|
||||
src={ChevronDown}
|
||||
@@ -188,7 +193,7 @@ const ConversationBubble = forwardRef<
|
||||
setIsEditClicked(true);
|
||||
setEditInputBox(message ?? '');
|
||||
}}
|
||||
className={`hover:bg-light-silver mt-3 flex h-fit shrink-0 cursor-pointer items-center rounded-full p-2 dark:hover:bg-[#35363B] ${isQuestionHovered || isEditClicked ? 'visible' : 'invisible'}`}
|
||||
className={`mt-3 flex h-fit flex-shrink-0 cursor-pointer items-center rounded-full p-2 hover:bg-light-silver dark:hover:bg-[#35363B] ${isQuestionHovered || isEditClicked ? 'visible' : 'invisible'}`}
|
||||
>
|
||||
<img src={Edit} alt="Edit" className="cursor-pointer" />
|
||||
</button>
|
||||
@@ -213,17 +218,17 @@ const ConversationBubble = forwardRef<
|
||||
}}
|
||||
rows={5}
|
||||
value={editInputBox}
|
||||
className="border-silver text-carbon dark:border-philippine-grey dark:bg-raisin-black dark:text-chinese-white w-full resize-none rounded-3xl border px-4 py-3 text-base leading-relaxed focus:outline-hidden"
|
||||
className="w-full resize-none rounded-3xl border border-silver px-4 py-3 text-base leading-relaxed text-carbon focus:outline-none dark:border-philippine-grey dark:bg-raisin-black dark:text-chinese-white"
|
||||
/>
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<button
|
||||
className="text-purple-30 hover:bg-gainsboro hover:text-chinese-black-2 dark:hover:bg-onyx-2 rounded-full px-4 py-2 text-sm font-semibold transition-colors dark:hover:text-[#B9BCBE]"
|
||||
className="rounded-full px-4 py-2 text-sm font-semibold text-purple-30 transition-colors hover:bg-gainsboro hover:text-chinese-black-2 dark:hover:bg-onyx-2 dark:hover:text-[#B9BCBE]"
|
||||
onClick={() => setIsEditClicked(false)}
|
||||
>
|
||||
{t('conversation.edit.cancel')}
|
||||
</button>
|
||||
<button
|
||||
className="bg-purple-30 hover:bg-violets-are-blue dark:hover:bg-royal-purple rounded-full px-4 py-2 text-sm font-medium text-white transition-colors"
|
||||
className="rounded-full bg-purple-30 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-violets-are-blue dark:hover:bg-royal-purple"
|
||||
onClick={handleEditClick}
|
||||
>
|
||||
{t('conversation.edit.update')}
|
||||
@@ -283,106 +288,141 @@ const ConversationBubble = forwardRef<
|
||||
bubble = (
|
||||
<div
|
||||
ref={ref}
|
||||
className={`flex flex-wrap self-start ${className} group dark:text-bright-gray flex-col`}
|
||||
className={`flex flex-wrap self-start ${className} group flex-col dark:text-bright-gray`}
|
||||
>
|
||||
{DisableSourceFE ||
|
||||
type === 'ERROR' ||
|
||||
sources?.length === 0 ||
|
||||
sources?.some((source) => source.link === 'None')
|
||||
? null
|
||||
: sources && (
|
||||
<div className="mb-4 flex flex-col flex-wrap items-start self-start lg:flex-nowrap">
|
||||
<div className="my-2 flex flex-row items-center justify-center gap-3">
|
||||
<Avatar
|
||||
className="h-[26px] w-[30px] text-xl"
|
||||
avatar={
|
||||
<img
|
||||
src={Sources}
|
||||
alt={t('conversation.sources.title')}
|
||||
className="h-full w-full object-fill"
|
||||
/>
|
||||
}
|
||||
sources?.some((source) => source.link === 'None') ? null : !sources &&
|
||||
chunks !== '0' &&
|
||||
selectedDocs ? (
|
||||
<div className="mb-4 flex flex-col flex-wrap items-start self-start lg:flex-nowrap">
|
||||
<div className="my-2 flex flex-row items-center justify-center gap-3">
|
||||
<Avatar
|
||||
className="h-[26px] w-[30px] text-xl"
|
||||
avatar={
|
||||
<img
|
||||
src={Sources}
|
||||
alt={t('conversation.sources.title')}
|
||||
className="h-full w-full object-fill"
|
||||
/>
|
||||
<p className="text-base font-semibold">
|
||||
{t('conversation.sources.title')}
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
<p className="text-base font-semibold">
|
||||
{t('conversation.sources.title')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-2 lg:grid-cols-4">
|
||||
{Array.from({ length: 4 }).map((_, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="flex h-28 cursor-pointer flex-col items-start gap-1 rounded-[20px] bg-gray-1000 p-4 text-purple-30 hover:bg-[#F1F1F1] hover:text-[#6D3ECC] dark:bg-gun-metal dark:hover:bg-[#2C2E3C] dark:hover:text-[#8C67D7]"
|
||||
>
|
||||
<span className="h-px w-10 animate-pulse cursor-pointer rounded-[20px] bg-[#B2B2B2] p-1"></span>
|
||||
<span className="h-px w-24 animate-pulse cursor-pointer rounded-[20px] bg-[#B2B2B2] p-1"></span>
|
||||
<span className="h-px w-16 animate-pulse cursor-pointer rounded-[20px] bg-[#B2B2B2] p-1"></span>
|
||||
<span className="h-px w-32 animate-pulse cursor-pointer rounded-[20px] bg-[#B2B2B2] p-1"></span>
|
||||
<span className="h-px w-24 animate-pulse cursor-pointer rounded-[20px] bg-[#B2B2B2] p-1"></span>
|
||||
<span className="h-px w-20 animate-pulse cursor-pointer rounded-[20px] bg-[#B2B2B2] p-1"></span>
|
||||
</div>
|
||||
<div className="fade-in mr-5 ml-3 max-w-[90vw] md:max-w-[70vw] lg:max-w-[50vw]">
|
||||
<div className="grid grid-cols-2 gap-2 lg:grid-cols-4">
|
||||
{sources?.slice(0, 3)?.map((source, index) => (
|
||||
<div key={index} className="relative">
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
sources && (
|
||||
<div className="mb-4 flex flex-col flex-wrap items-start self-start lg:flex-nowrap">
|
||||
<div className="my-2 flex flex-row items-center justify-center gap-3">
|
||||
<Avatar
|
||||
className="h-[26px] w-[30px] text-xl"
|
||||
avatar={
|
||||
<img
|
||||
src={Sources}
|
||||
alt={t('conversation.sources.title')}
|
||||
className="h-full w-full object-fill"
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<p className="text-base font-semibold">
|
||||
{t('conversation.sources.title')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="fade-in ml-3 mr-5 max-w-[90vw] md:max-w-[70vw] lg:max-w-[50vw]">
|
||||
<div className="grid grid-cols-2 gap-2 lg:grid-cols-4">
|
||||
{sources?.slice(0, 3)?.map((source, index) => (
|
||||
<div key={index} className="relative">
|
||||
<div
|
||||
className="h-28 cursor-pointer rounded-[20px] bg-gray-1000 p-4 hover:bg-[#F1F1F1] dark:bg-gun-metal dark:hover:bg-[#2C2E3C]"
|
||||
onMouseOver={() => setActiveTooltip(index)}
|
||||
onMouseOut={() => setActiveTooltip(null)}
|
||||
>
|
||||
<p className="ellipsis-text h-12 break-words text-xs">
|
||||
{source.text}
|
||||
</p>
|
||||
<div
|
||||
className="bg-gray-1000 dark:bg-gun-metal h-28 cursor-pointer rounded-[20px] p-4 hover:bg-[#F1F1F1] dark:hover:bg-[#2C2E3C]"
|
||||
className={`mt-[14px] flex flex-row items-center gap-[6px] underline-offset-2 ${
|
||||
source.link && source.link !== 'local'
|
||||
? 'hover:text-[#007DFF] hover:underline dark:hover:text-[#48A0FF]'
|
||||
: ''
|
||||
}`}
|
||||
onClick={() =>
|
||||
source.link && source.link !== 'local'
|
||||
? window.open(
|
||||
source.link,
|
||||
'_blank',
|
||||
'noopener, noreferrer',
|
||||
)
|
||||
: null
|
||||
}
|
||||
>
|
||||
<img
|
||||
src={Document}
|
||||
alt="Document"
|
||||
className="h-[17px] w-[17px] object-fill"
|
||||
/>
|
||||
<p
|
||||
className="mt-[2px] truncate text-xs"
|
||||
title={
|
||||
source.link && source.link !== 'local'
|
||||
? source.link
|
||||
: source.title
|
||||
}
|
||||
>
|
||||
{source.link && source.link !== 'local'
|
||||
? source.link
|
||||
: source.title}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{activeTooltip === index && (
|
||||
<div
|
||||
className={`absolute left-1/2 z-50 max-h-48 w-40 translate-x-[-50%] translate-y-[3px] rounded-xl bg-[#FBFBFB] p-4 text-black shadow-xl dark:bg-chinese-black dark:text-chinese-silver sm:w-56`}
|
||||
onMouseOver={() => setActiveTooltip(index)}
|
||||
onMouseOut={() => setActiveTooltip(null)}
|
||||
>
|
||||
<p className="ellipsis-text h-12 text-xs break-words">
|
||||
<p className="line-clamp-6 max-h-[164px] overflow-hidden text-ellipsis break-words rounded-md text-sm">
|
||||
{source.text}
|
||||
</p>
|
||||
<div
|
||||
className={`mt-[14px] flex flex-row items-center gap-[6px] underline-offset-2 ${
|
||||
source.link && source.link !== 'local'
|
||||
? 'hover:text-[#007DFF] hover:underline dark:hover:text-[#48A0FF]'
|
||||
: ''
|
||||
}`}
|
||||
onClick={() =>
|
||||
source.link && source.link !== 'local'
|
||||
? window.open(
|
||||
source.link,
|
||||
'_blank',
|
||||
'noopener, noreferrer',
|
||||
)
|
||||
: null
|
||||
}
|
||||
>
|
||||
<img
|
||||
src={Document}
|
||||
alt="Document"
|
||||
className="h-[17px] w-[17px] object-fill"
|
||||
/>
|
||||
<p
|
||||
className="mt-[2px] truncate text-xs"
|
||||
title={
|
||||
source.link && source.link !== 'local'
|
||||
? source.link
|
||||
: source.title
|
||||
}
|
||||
>
|
||||
{source.link && source.link !== 'local'
|
||||
? source.link
|
||||
: source.title}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{activeTooltip === index && (
|
||||
<div
|
||||
className={`dark:bg-chinese-black dark:text-chinese-silver absolute left-1/2 z-50 max-h-48 w-40 translate-x-[-50%] translate-y-[3px] rounded-xl bg-[#FBFBFB] p-4 text-black shadow-xl sm:w-56`}
|
||||
onMouseOver={() => setActiveTooltip(index)}
|
||||
onMouseOut={() => setActiveTooltip(null)}
|
||||
>
|
||||
<p className="line-clamp-6 max-h-[164px] overflow-hidden rounded-md text-sm break-words text-ellipsis">
|
||||
{source.text}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{(sources?.length ?? 0) > 3 && (
|
||||
<div
|
||||
className="bg-gray-1000 text-purple-30 dark:bg-gun-metal flex h-28 cursor-pointer flex-col-reverse rounded-[20px] p-4 hover:bg-[#F1F1F1] hover:text-[#6D3ECC] dark:hover:bg-[#2C2E3C] dark:hover:text-[#8C67D7]"
|
||||
onClick={() => setIsSidebarOpen(true)}
|
||||
>
|
||||
<p className="ellipsis-text h-22 text-xs">
|
||||
{t('conversation.sources.view_more', {
|
||||
count: sources?.length ? sources.length - 3 : 0,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{(sources?.length ?? 0) > 3 && (
|
||||
<div
|
||||
className="flex h-28 cursor-pointer flex-col-reverse rounded-[20px] bg-gray-1000 p-4 text-purple-30 hover:bg-[#F1F1F1] hover:text-[#6D3ECC] dark:bg-gun-metal dark:hover:bg-[#2C2E3C] dark:hover:text-[#8C67D7]"
|
||||
onClick={() => setIsSidebarOpen(true)}
|
||||
>
|
||||
<p className="ellipsis-text h-22 text-xs">
|
||||
{t('conversation.sources.view_more', {
|
||||
count: sources?.length ? sources.length - 3 : 0,
|
||||
})}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
{toolCalls && toolCalls.length > 0 && (
|
||||
<ToolCalls toolCalls={toolCalls} />
|
||||
)}
|
||||
@@ -407,9 +447,9 @@ const ConversationBubble = forwardRef<
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className={`fade-in-bubble bg-gray-1000 dark:bg-gun-metal mr-5 flex max-w-full rounded-[28px] px-7 py-[18px] ${
|
||||
className={`fade-in-bubble mr-5 flex max-w-full rounded-[28px] bg-gray-1000 px-7 py-[18px] dark:bg-gun-metal ${
|
||||
type === 'ERROR'
|
||||
? 'text-red-3000 dark:border-red-2000 relative flex-row items-center rounded-full border border-transparent bg-[#FFE7E7] p-2 py-5 text-sm font-normal dark:text-white'
|
||||
? 'relative flex-row items-center rounded-full border border-transparent bg-[#FFE7E7] p-2 py-5 text-sm font-normal text-red-3000 dark:border-red-2000 dark:text-white'
|
||||
: 'flex-col rounded-3xl'
|
||||
}`}
|
||||
>
|
||||
@@ -421,7 +461,7 @@ const ConversationBubble = forwardRef<
|
||||
<Fragment key={index}>
|
||||
{segment.type === 'text' ? (
|
||||
<ReactMarkdown
|
||||
className="fade-in leading-normal break-words whitespace-pre-wrap"
|
||||
className="fade-in whitespace-pre-wrap break-words leading-normal"
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[rehypeKatex]}
|
||||
components={{
|
||||
@@ -439,9 +479,9 @@ const ConversationBubble = forwardRef<
|
||||
const language = match ? match[1] : '';
|
||||
|
||||
return match ? (
|
||||
<div className="group border-light-silver dark:border-raisin-black relative overflow-hidden rounded-[14px] border">
|
||||
<div className="bg-platinum dark:bg-eerie-black-2 flex items-center justify-between px-2 py-1">
|
||||
<span className="text-just-black dark:text-chinese-white text-xs font-medium">
|
||||
<div className="group relative overflow-hidden rounded-[14px] border border-light-silver dark:border-raisin-black">
|
||||
<div className="flex items-center justify-between bg-platinum px-2 py-1 dark:bg-eerie-black-2">
|
||||
<span className="text-xs font-medium text-just-black dark:text-chinese-white">
|
||||
{language}
|
||||
</span>
|
||||
<CopyButton
|
||||
@@ -458,7 +498,7 @@ const ConversationBubble = forwardRef<
|
||||
style={
|
||||
isDarkTheme ? vscDarkPlus : oneLight
|
||||
}
|
||||
className="mt-0!"
|
||||
className="!mt-0"
|
||||
customStyle={{
|
||||
margin: 0,
|
||||
borderRadius: 0,
|
||||
@@ -469,7 +509,7 @@ const ConversationBubble = forwardRef<
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
) : (
|
||||
<code className="dark:bg-independence dark:text-bright-gray rounded-[6px] bg-gray-200 px-[8px] py-[4px] text-xs font-normal whitespace-pre-line">
|
||||
<code className="whitespace-pre-line rounded-[6px] bg-gray-200 px-[8px] py-[4px] text-xs font-normal dark:bg-independence dark:text-bright-gray">
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
@@ -477,7 +517,7 @@ const ConversationBubble = forwardRef<
|
||||
ul({ children }) {
|
||||
return (
|
||||
<ul
|
||||
className={`list-inside list-disc pl-4 whitespace-normal ${classes.list}`}
|
||||
className={`list-inside list-disc whitespace-normal pl-4 ${classes.list}`}
|
||||
>
|
||||
{children}
|
||||
</ul>
|
||||
@@ -486,7 +526,7 @@ const ConversationBubble = forwardRef<
|
||||
ol({ children }) {
|
||||
return (
|
||||
<ol
|
||||
className={`list-inside list-decimal pl-4 whitespace-normal ${classes.list}`}
|
||||
className={`list-inside list-decimal whitespace-normal pl-4 ${classes.list}`}
|
||||
>
|
||||
{children}
|
||||
</ol>
|
||||
@@ -494,8 +534,8 @@ const ConversationBubble = forwardRef<
|
||||
},
|
||||
table({ children }) {
|
||||
return (
|
||||
<div className="border-silver/40 dark:border-silver/40 relative overflow-x-auto rounded-lg border">
|
||||
<table className="dark:text-bright-gray w-full text-left text-gray-700">
|
||||
<div className="relative overflow-x-auto rounded-lg border border-silver/40 dark:border-silver/40">
|
||||
<table className="w-full text-left text-gray-700 dark:text-bright-gray">
|
||||
{children}
|
||||
</table>
|
||||
</div>
|
||||
@@ -503,14 +543,14 @@ const ConversationBubble = forwardRef<
|
||||
},
|
||||
thead({ children }) {
|
||||
return (
|
||||
<thead className="dark:text-bright-gray bg-gray-50 text-xs text-gray-900 uppercase dark:bg-[#26272E]/50">
|
||||
<thead className="bg-gray-50 text-xs uppercase text-gray-900 dark:bg-[#26272E]/50 dark:text-bright-gray">
|
||||
{children}
|
||||
</thead>
|
||||
);
|
||||
},
|
||||
tr({ children }) {
|
||||
return (
|
||||
<tr className="dark:border-silver/40 border-b border-gray-200 odd:bg-white even:bg-gray-50 dark:odd:bg-[#26272E] dark:even:bg-[#26272E]/50">
|
||||
<tr className="border-b border-gray-200 odd:bg-white even:bg-gray-50 dark:border-silver/40 dark:odd:bg-[#26272E] dark:even:bg-[#26272E]/50">
|
||||
{children}
|
||||
</tr>
|
||||
);
|
||||
@@ -551,14 +591,14 @@ const ConversationBubble = forwardRef<
|
||||
{message && (
|
||||
<div className="my-2 ml-2 flex justify-start">
|
||||
<div
|
||||
className={`relative mr-2 block items-center justify-center lg:invisible ${type !== 'ERROR' ? 'lg:group-hover:visible' : 'hidden'}`}
|
||||
className={`relative mr-2 block items-center justify-center lg:invisible ${type !== 'ERROR' ? 'group-hover:lg:visible' : 'hidden'}`}
|
||||
>
|
||||
<div>
|
||||
<CopyButton textToCopy={message} />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={`relative mr-2 block items-center justify-center lg:invisible ${type !== 'ERROR' ? 'lg:group-hover:visible' : 'hidden'}`}
|
||||
className={`relative mr-2 block items-center justify-center lg:invisible ${type !== 'ERROR' ? 'group-hover:lg:visible' : 'hidden'}`}
|
||||
>
|
||||
<div>
|
||||
<SpeakButton text={message} />
|
||||
@@ -576,21 +616,21 @@ const ConversationBubble = forwardRef<
|
||||
feedback === 'LIKE' || isLikeClicked
|
||||
? 'visible'
|
||||
: 'lg:invisible'
|
||||
} ${type !== 'ERROR' ? 'lg:group-hover:visible' : ''} ${feedback === 'DISLIKE' && type !== 'ERROR' ? 'hidden' : ''}`}
|
||||
} ${type !== 'ERROR' ? 'group-hover:lg:visible' : ''} ${feedback === 'DISLIKE' && type !== 'ERROR' ? 'hidden' : ''}`}
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className={`flex items-center justify-center rounded-full p-2 ${
|
||||
isLikeHovered
|
||||
? 'dark:bg-purple-taupe bg-[#EEEEEE]'
|
||||
: 'bg-white-3000 dark:bg-transparent'
|
||||
? 'bg-[#EEEEEE] dark:bg-purple-taupe'
|
||||
: 'bg-[#ffffff] dark:bg-transparent'
|
||||
}`}
|
||||
>
|
||||
<Like
|
||||
className={`cursor-pointer ${
|
||||
isLikeClicked || feedback === 'LIKE'
|
||||
? 'fill-white-3000 stroke-purple-30 dark:fill-transparent'
|
||||
: 'stroke-gray-4000 fill-none'
|
||||
: 'fill-none stroke-gray-4000'
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (feedback === undefined || feedback === null) {
|
||||
@@ -615,21 +655,21 @@ const ConversationBubble = forwardRef<
|
||||
feedback === 'DISLIKE' || isLikeClicked
|
||||
? 'visible'
|
||||
: 'lg:invisible'
|
||||
} ${type !== 'ERROR' ? 'lg:group-hover:visible' : ''} ${feedback === 'LIKE' && type !== 'ERROR' ? 'hidden' : ''}`}
|
||||
} ${type !== 'ERROR' ? 'group-hover:lg:visible' : ''} ${feedback === 'LIKE' && type !== 'ERROR' ? 'hidden' : ''}`}
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className={`flex items-center justify-center rounded-full p-2 ${
|
||||
isDislikeHovered
|
||||
? 'dark:bg-purple-taupe bg-[#EEEEEE]'
|
||||
: 'bg-white-3000 dark:bg-transparent'
|
||||
? 'bg-[#EEEEEE] dark:bg-purple-taupe'
|
||||
: 'bg-[#ffffff] dark:bg-transparent'
|
||||
}`}
|
||||
>
|
||||
<Dislike
|
||||
className={`cursor-pointer ${
|
||||
isDislikeClicked || feedback === 'DISLIKE'
|
||||
? 'fill-white-3000 stroke-red-2000 dark:fill-transparent'
|
||||
: 'stroke-gray-4000 fill-none'
|
||||
: 'fill-none stroke-gray-4000'
|
||||
}`}
|
||||
onClick={() => {
|
||||
if (feedback === undefined || feedback === null) {
|
||||
@@ -693,7 +733,7 @@ function AllSources(sources: AllSourcesProps) {
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className={`group/card bg-gray-1000 relative w-full rounded-[20px] p-4 transition-colors hover:bg-[#F1F1F1] dark:bg-[#28292E] dark:hover:bg-[#2C2E3C] ${
|
||||
className={`group/card relative w-full rounded-[20px] bg-gray-1000 p-4 transition-colors hover:bg-[#F1F1F1] dark:bg-[#28292E] dark:hover:bg-[#2C2E3C] ${
|
||||
isExternalSource ? 'cursor-pointer' : ''
|
||||
}`}
|
||||
onClick={() =>
|
||||
@@ -702,7 +742,7 @@ function AllSources(sources: AllSourcesProps) {
|
||||
>
|
||||
<p
|
||||
title={source.title}
|
||||
className={`ellipsis-text text-left text-sm font-semibold break-words ${
|
||||
className={`ellipsis-text break-words text-left text-sm font-semibold ${
|
||||
isExternalSource
|
||||
? 'group-hover/card:text-purple-30 dark:group-hover/card:text-[#8C67D7]'
|
||||
: ''
|
||||
@@ -715,13 +755,13 @@ function AllSources(sources: AllSourcesProps) {
|
||||
alt="External Link"
|
||||
className={`ml-1 inline h-3 w-3 object-fill dark:invert ${
|
||||
isExternalSource
|
||||
? 'group-hover/card:contrast-50 group-hover/card:hue-rotate-235 group-hover/card:invert-31 group-hover/card:saturate-752 group-hover/card:sepia-80 group-hover/card:filter'
|
||||
? 'group-hover/card:contrast-[50%] group-hover/card:hue-rotate-[235deg] group-hover/card:invert-[31%] group-hover/card:saturate-[752%] group-hover/card:sepia-[80%] group-hover/card:filter'
|
||||
: ''
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
</p>
|
||||
<p className="dark:text-chinese-silver mt-3 line-clamp-4 rounded-md text-left text-xs break-words text-black">
|
||||
<p className="mt-3 line-clamp-4 break-words rounded-md text-left text-xs text-black dark:text-chinese-silver">
|
||||
{source.text}
|
||||
</p>
|
||||
</div>
|
||||
@@ -761,18 +801,18 @@ function ToolCalls({ toolCalls }: { toolCalls: ToolCallsType[] }) {
|
||||
</button>
|
||||
</div>
|
||||
{isToolCallsOpen && (
|
||||
<div className="fade-in mr-5 ml-3 w-[90vw] md:w-[70vw] lg:w-full">
|
||||
<div className="fade-in ml-3 mr-5 w-[90vw] md:w-[70vw] lg:w-full">
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
{toolCalls.map((toolCall, index) => (
|
||||
<Accordion
|
||||
key={`tool-call-${index}`}
|
||||
title={`${toolCall.tool_name} - ${toolCall.action_name.substring(0, toolCall.action_name.lastIndexOf('_'))}`}
|
||||
className="bg-gray-1000 dark:bg-gun-metal w-full rounded-[20px] hover:bg-[#F1F1F1] dark:hover:bg-[#2C2E3C]"
|
||||
className="w-full rounded-[20px] bg-gray-1000 hover:bg-[#F1F1F1] dark:bg-gun-metal dark:hover:bg-[#2C2E3C]"
|
||||
titleClassName="px-6 py-2 text-sm font-semibold"
|
||||
>
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="border-silver dark:border-silver/20 flex flex-col rounded-2xl border">
|
||||
<p className="dark:bg-eerie-black-2 flex flex-row items-center justify-between rounded-t-2xl bg-black/10 px-2 py-1 text-sm font-semibold break-words">
|
||||
<div className="flex flex-col rounded-2xl border border-silver dark:border-silver/20">
|
||||
<p className="flex flex-row items-center justify-between break-words rounded-t-2xl bg-black/10 px-2 py-1 text-sm font-semibold dark:bg-[#191919]">
|
||||
<span style={{ fontFamily: 'IBMPlexMono-Medium' }}>
|
||||
Arguments
|
||||
</span>{' '}
|
||||
@@ -780,7 +820,7 @@ function ToolCalls({ toolCalls }: { toolCalls: ToolCallsType[] }) {
|
||||
textToCopy={JSON.stringify(toolCall.arguments, null, 2)}
|
||||
/>
|
||||
</p>
|
||||
<p className="dark:tex dark:bg-raisin-black rounded-b-2xl p-2 font-mono text-sm break-words">
|
||||
<p className="dark:tex break-words rounded-b-2xl p-2 font-mono text-sm dark:bg-[#222327]">
|
||||
<span
|
||||
className="leading-[23px] text-black dark:text-gray-400"
|
||||
style={{ fontFamily: 'IBMPlexMono-Medium' }}
|
||||
@@ -789,8 +829,8 @@ function ToolCalls({ toolCalls }: { toolCalls: ToolCallsType[] }) {
|
||||
</span>
|
||||
</p>
|
||||
</div>
|
||||
<div className="border-silver dark:border-silver/20 flex flex-col rounded-2xl border">
|
||||
<p className="dark:bg-eerie-black-2 flex flex-row items-center justify-between rounded-t-2xl bg-black/10 px-2 py-1 text-sm font-semibold break-words">
|
||||
<div className="flex flex-col rounded-2xl border border-silver dark:border-silver/20">
|
||||
<p className="flex flex-row items-center justify-between break-words rounded-t-2xl bg-black/10 px-2 py-1 text-sm font-semibold dark:bg-[#191919]">
|
||||
<span style={{ fontFamily: 'IBMPlexMono-Medium' }}>
|
||||
Response
|
||||
</span>{' '}
|
||||
@@ -799,12 +839,12 @@ function ToolCalls({ toolCalls }: { toolCalls: ToolCallsType[] }) {
|
||||
/>
|
||||
</p>
|
||||
{toolCall.status === 'pending' && (
|
||||
<span className="dark:bg-raisin-black flex w-full items-center justify-center rounded-b-2xl p-2">
|
||||
<span className="flex w-full items-center justify-center rounded-b-2xl p-2 dark:bg-[#222327]">
|
||||
<Spinner size="small" />
|
||||
</span>
|
||||
)}
|
||||
{toolCall.status === 'completed' && (
|
||||
<p className="dark:bg-raisin-black rounded-b-2xl p-2 font-mono text-sm break-words">
|
||||
<p className="break-words rounded-b-2xl p-2 font-mono text-sm dark:bg-[#222327]">
|
||||
<span
|
||||
className="leading-[23px] text-black dark:text-gray-400"
|
||||
style={{ fontFamily: 'IBMPlexMono-Medium' }}
|
||||
@@ -860,10 +900,10 @@ function Thought({
|
||||
</button>
|
||||
</div>
|
||||
{isThoughtOpen && (
|
||||
<div className="fade-in mr-5 ml-2 max-w-[90vw] md:max-w-[70vw] lg:max-w-[50vw]">
|
||||
<div className="bg-gray-1000 dark:bg-gun-metal rounded-[28px] px-7 py-[18px]">
|
||||
<div className="fade-in ml-2 mr-5 max-w-[90vw] md:max-w-[70vw] lg:max-w-[50vw]">
|
||||
<div className="rounded-[28px] bg-gray-1000 px-7 py-[18px] dark:bg-gun-metal">
|
||||
<ReactMarkdown
|
||||
className="fade-in leading-normal break-words whitespace-pre-wrap"
|
||||
className="fade-in whitespace-pre-wrap break-words leading-normal"
|
||||
remarkPlugins={[remarkGfm, remarkMath]}
|
||||
rehypePlugins={[rehypeKatex]}
|
||||
components={{
|
||||
@@ -873,9 +913,9 @@ function Thought({
|
||||
const language = match ? match[1] : '';
|
||||
|
||||
return match ? (
|
||||
<div className="group border-light-silver dark:border-raisin-black relative overflow-hidden rounded-[14px] border">
|
||||
<div className="bg-platinum dark:bg-eerie-black-2 flex items-center justify-between px-2 py-1">
|
||||
<span className="text-just-black dark:text-chinese-white text-xs font-medium">
|
||||
<div className="group relative overflow-hidden rounded-[14px] border border-light-silver dark:border-raisin-black">
|
||||
<div className="flex items-center justify-between bg-platinum px-2 py-1 dark:bg-eerie-black-2">
|
||||
<span className="text-xs font-medium text-just-black dark:text-chinese-white">
|
||||
{language}
|
||||
</span>
|
||||
<CopyButton
|
||||
@@ -887,7 +927,7 @@ function Thought({
|
||||
PreTag="div"
|
||||
language={language}
|
||||
style={isDarkTheme ? vscDarkPlus : oneLight}
|
||||
className="mt-0!"
|
||||
className="!mt-0"
|
||||
customStyle={{
|
||||
margin: 0,
|
||||
borderRadius: 0,
|
||||
@@ -898,29 +938,29 @@ function Thought({
|
||||
</SyntaxHighlighter>
|
||||
</div>
|
||||
) : (
|
||||
<code className="dark:bg-independence dark:text-bright-gray rounded-[6px] bg-gray-200 px-[8px] py-[4px] text-xs font-normal whitespace-pre-line">
|
||||
<code className="whitespace-pre-line rounded-[6px] bg-gray-200 px-[8px] py-[4px] text-xs font-normal dark:bg-independence dark:text-bright-gray">
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
},
|
||||
ul({ children }) {
|
||||
return (
|
||||
<ul className="list-inside list-disc pl-4 whitespace-normal">
|
||||
<ul className="list-inside list-disc whitespace-normal pl-4">
|
||||
{children}
|
||||
</ul>
|
||||
);
|
||||
},
|
||||
ol({ children }) {
|
||||
return (
|
||||
<ol className="list-inside list-decimal pl-4 whitespace-normal">
|
||||
<ol className="list-inside list-decimal whitespace-normal pl-4">
|
||||
{children}
|
||||
</ol>
|
||||
);
|
||||
},
|
||||
table({ children }) {
|
||||
return (
|
||||
<div className="border-silver/40 dark:border-silver/40 relative overflow-x-auto rounded-lg border">
|
||||
<table className="dark:text-bright-gray w-full text-left text-gray-700">
|
||||
<div className="relative overflow-x-auto rounded-lg border border-silver/40 dark:border-silver/40">
|
||||
<table className="w-full text-left text-gray-700 dark:text-bright-gray">
|
||||
{children}
|
||||
</table>
|
||||
</div>
|
||||
@@ -928,14 +968,14 @@ function Thought({
|
||||
},
|
||||
thead({ children }) {
|
||||
return (
|
||||
<thead className="dark:text-bright-gray bg-gray-50 text-xs text-gray-900 uppercase dark:bg-[#26272E]/50">
|
||||
<thead className="bg-gray-50 text-xs uppercase text-gray-900 dark:bg-[#26272E]/50 dark:text-bright-gray">
|
||||
{children}
|
||||
</thead>
|
||||
);
|
||||
},
|
||||
tr({ children }) {
|
||||
return (
|
||||
<tr className="dark:border-silver/40 border-b border-gray-200 odd:bg-white even:bg-gray-50 dark:odd:bg-[#26272E] dark:even:bg-[#26272E]/50">
|
||||
<tr className="border-b border-gray-200 odd:bg-white even:bg-gray-50 dark:border-silver/40 dark:odd:bg-[#26272E] dark:even:bg-[#26272E]/50">
|
||||
{children}
|
||||
</tr>
|
||||
);
|
||||
|
||||
@@ -192,7 +192,7 @@ export default function ConversationTile({
|
||||
conversationId !== conversation.id &&
|
||||
selectConversation(conversation.id);
|
||||
}}
|
||||
className={`hover:bg-bright-gray dark:hover:bg-dark-charcoal mx-4 my-auto mt-4 flex h-9 cursor-pointer items-center justify-between gap-4 rounded-3xl pl-4 ${
|
||||
className={`mx-4 my-auto mt-4 flex h-9 cursor-pointer items-center justify-between gap-4 rounded-3xl pl-4 hover:bg-bright-gray dark:hover:bg-dark-charcoal ${
|
||||
conversationId === conversation.id || isOpen || isHovered || isEdit
|
||||
? 'bg-bright-gray dark:bg-dark-charcoal'
|
||||
: ''
|
||||
@@ -203,19 +203,19 @@ export default function ConversationTile({
|
||||
<input
|
||||
autoFocus
|
||||
type="text"
|
||||
className="h-6 w-full bg-transparent px-1 text-sm leading-6 font-normal focus:outline-[#0075FF]"
|
||||
className="h-6 w-full bg-transparent px-1 text-sm font-normal leading-6 focus:outline-[#0075FF]"
|
||||
value={conversationName}
|
||||
onChange={(e) => setConversationsName(e.target.value)}
|
||||
onKeyDown={handleRenameKeyDown}
|
||||
/>
|
||||
) : (
|
||||
<p className="text-eerie-black dark:text-bright-gray my-auto overflow-hidden text-sm leading-6 font-normal text-ellipsis whitespace-nowrap">
|
||||
<p className="my-auto overflow-hidden overflow-ellipsis whitespace-nowrap text-sm font-normal leading-6 text-eerie-black dark:text-bright-gray">
|
||||
{conversationName}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
{(conversationId === conversation.id || isHovered || isOpen) && (
|
||||
<div className="dark:text-sonic-silver flex text-white" ref={menuRef}>
|
||||
<div className="flex text-white dark:text-sonic-silver" ref={menuRef}>
|
||||
{isEdit ? (
|
||||
<div className="flex gap-1">
|
||||
<img
|
||||
@@ -234,7 +234,7 @@ export default function ConversationTile({
|
||||
<img
|
||||
src={Exit}
|
||||
alt="Exit"
|
||||
className={`mt-px mr-4 h-3 w-3 cursor-pointer filter hover:opacity-50 dark:invert`}
|
||||
className={`mr-4 mt-px h-3 w-3 cursor-pointer filter hover:opacity-50 dark:invert`}
|
||||
id={`img-${conversation.id}`}
|
||||
onClick={(event: SyntheticEvent) => {
|
||||
event.stopPropagation();
|
||||
@@ -250,7 +250,7 @@ export default function ConversationTile({
|
||||
}}
|
||||
className="mr-2 flex w-4 justify-center"
|
||||
>
|
||||
<img src={threeDots} width={8} alt="menu" />
|
||||
<img src={threeDots} width={8} />
|
||||
</button>
|
||||
)}
|
||||
<ContextMenu
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Helmet } from 'react-helmet';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
@@ -23,7 +24,6 @@ import {
|
||||
updateQuery,
|
||||
} from './sharedConversationSlice';
|
||||
import { selectCompletedAttachments } from '../upload/uploadSlice';
|
||||
import { DocumentHead } from '../components/DocumentHead';
|
||||
|
||||
export const SharedConversation = () => {
|
||||
const navigate = useNavigate();
|
||||
@@ -129,27 +129,33 @@ export const SharedConversation = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<DocumentHead
|
||||
title={`DocsGPT | ${title}`}
|
||||
description="Shared conversations with DocsGPT"
|
||||
ogTitle={title}
|
||||
ogDescription="Shared conversations with DocsGPT"
|
||||
twitterCard="summary_large_image"
|
||||
twitterTitle={title}
|
||||
twitterDescription="Shared conversations with DocsGPT"
|
||||
/>
|
||||
<div className="dark:bg-raisin-black flex h-full flex-col items-center justify-between gap-2 overflow-y-hidden">
|
||||
<div className="dark:border-b-silver w-full max-w-[1200px] border-b p-2 md:w-9/12 lg:w-8/12 xl:w-8/12 2xl:w-6/12">
|
||||
<h1 className="font-semi-bold text-chinese-black dark:text-chinese-silver text-4xl">
|
||||
<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 className="w-full max-w-[1200px] border-b p-2 dark:border-b-silver md:w-9/12 lg:w-8/12 xl:w-8/12 2xl:w-6/12">
|
||||
<h1 className="font-semi-bold text-4xl text-chinese-black dark:text-chinese-silver">
|
||||
{title}
|
||||
</h1>
|
||||
<h2 className="font-semi-bold text-chinese-black dark:text-chinese-silver text-base">
|
||||
<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-chinese-black dark:text-chinese-silver text-base">
|
||||
<h2 className="font-semi-bold text-base text-chinese-black dark:text-chinese-silver">
|
||||
{date}
|
||||
</h2>
|
||||
</div>
|
||||
@@ -172,13 +178,13 @@ export const SharedConversation = () => {
|
||||
) : (
|
||||
<button
|
||||
onClick={() => navigate('/')}
|
||||
className="bg-purple-30 hover:bg-violets-are-blue mb-14 w-fit rounded-full px-5 py-3 text-white shadow-xl transition-colors duration-200 sm:mb-0"
|
||||
className="mb-14 w-fit rounded-full bg-purple-30 px-5 py-3 text-white shadow-xl transition-colors duration-200 hover:bg-violets-are-blue sm:mb-0"
|
||||
>
|
||||
{t('sharedConv.button')}
|
||||
</button>
|
||||
)}
|
||||
|
||||
<p className="text-gray-4000 dark:text-sonic-silver hidden w-screen self-center bg-transparent py-2 text-center text-xs md:inline md:w-full">
|
||||
<p className="hidden w-[100vw] self-center bg-transparent py-2 text-center text-xs text-gray-4000 dark:text-sonic-silver md:inline md:w-full">
|
||||
{t('sharedConv.meta')}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,13 @@ import {
|
||||
handleFetchAnswer,
|
||||
handleFetchAnswerSteaming,
|
||||
} from './conversationHandlers';
|
||||
import { Answer, ConversationState, Query, Status } from './conversationModels';
|
||||
import {
|
||||
Answer,
|
||||
Attachment,
|
||||
ConversationState,
|
||||
Query,
|
||||
Status,
|
||||
} from './conversationModels';
|
||||
import { ToolCallsType } from './types';
|
||||
|
||||
const initialState: ConversationState = {
|
||||
@@ -32,64 +38,65 @@ export function handleAbort() {
|
||||
|
||||
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;
|
||||
{ question: string; indx?: number; isPreview?: boolean }
|
||||
>(
|
||||
'fetchAnswer',
|
||||
async ({ question, indx, isPreview = false }, { dispatch, getState }) => {
|
||||
if (abortController) abortController.abort();
|
||||
abortController = new AbortController();
|
||||
const { signal } = abortController;
|
||||
|
||||
let isSourceUpdated = false;
|
||||
const state = getState() as RootState;
|
||||
const attachmentIds = selectCompletedAttachments(state)
|
||||
.filter((a) => a.id)
|
||||
.map((a) => a.id) as string[];
|
||||
let isSourceUpdated = false;
|
||||
const state = getState() as RootState;
|
||||
const attachmentIds = selectCompletedAttachments(state)
|
||||
.filter((a) => a.id)
|
||||
.map((a) => a.id) as string[];
|
||||
|
||||
if (attachmentIds.length > 0) {
|
||||
dispatch(clearAttachments());
|
||||
}
|
||||
if (attachmentIds.length > 0) {
|
||||
dispatch(clearAttachments());
|
||||
}
|
||||
|
||||
const currentConversationId = state.conversation.conversationId;
|
||||
const currentConversationId = state.conversation.conversationId;
|
||||
const conversationIdToSend = isPreview ? null : currentConversationId;
|
||||
const save_conversation = isPreview ? false : true;
|
||||
|
||||
if (state.preference) {
|
||||
if (API_STREAMING) {
|
||||
await handleFetchAnswerSteaming(
|
||||
question,
|
||||
signal,
|
||||
state.preference.token,
|
||||
state.preference.selectedDocs!,
|
||||
state.conversation.queries,
|
||||
currentConversationId,
|
||||
state.preference.prompt.id,
|
||||
state.preference.chunks,
|
||||
state.preference.token_limit,
|
||||
(event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
const targetIndex = indx ?? state.conversation.queries.length - 1;
|
||||
if (state.preference) {
|
||||
if (API_STREAMING) {
|
||||
await handleFetchAnswerSteaming(
|
||||
question,
|
||||
signal,
|
||||
state.preference.token,
|
||||
state.preference.selectedDocs!,
|
||||
state.conversation.queries,
|
||||
conversationIdToSend,
|
||||
state.preference.prompt.id,
|
||||
state.preference.chunks,
|
||||
state.preference.token_limit,
|
||||
(event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
const targetIndex = indx ?? state.conversation.queries.length - 1;
|
||||
|
||||
// Only process events if they match the current conversation
|
||||
if (currentConversationId === state.conversation.conversationId) {
|
||||
if (data.type === 'end') {
|
||||
dispatch(conversationSlice.actions.setStatus('idle'));
|
||||
getConversations(state.preference.token)
|
||||
.then((fetchedConversations) => {
|
||||
dispatch(setConversations(fetchedConversations));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to fetch conversations: ', error);
|
||||
});
|
||||
if (!isPreview) {
|
||||
getConversations(state.preference.token)
|
||||
.then((fetchedConversations) => {
|
||||
dispatch(setConversations(fetchedConversations));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to fetch conversations: ', error);
|
||||
});
|
||||
}
|
||||
if (!isSourceUpdated) {
|
||||
dispatch(
|
||||
updateStreamingSource({
|
||||
conversationId: currentConversationId,
|
||||
index: targetIndex,
|
||||
query: { sources: [] },
|
||||
}),
|
||||
);
|
||||
}
|
||||
} else if (data.type === 'id') {
|
||||
// Only update the conversationId if it's currently null
|
||||
const currentState = getState() as RootState;
|
||||
if (currentState.conversation.conversationId === null) {
|
||||
if (!isPreview) {
|
||||
dispatch(
|
||||
updateConversationId({
|
||||
query: { conversationId: data.id },
|
||||
@@ -100,7 +107,6 @@ export const fetchAnswer = createAsyncThunk<
|
||||
const result = data.thought;
|
||||
dispatch(
|
||||
updateThought({
|
||||
conversationId: currentConversationId,
|
||||
index: targetIndex,
|
||||
query: { thought: result },
|
||||
}),
|
||||
@@ -109,7 +115,6 @@ export const fetchAnswer = createAsyncThunk<
|
||||
isSourceUpdated = true;
|
||||
dispatch(
|
||||
updateStreamingSource({
|
||||
conversationId: currentConversationId,
|
||||
index: targetIndex,
|
||||
query: { sources: data.source ?? [] },
|
||||
}),
|
||||
@@ -126,7 +131,6 @@ export const fetchAnswer = createAsyncThunk<
|
||||
dispatch(conversationSlice.actions.setStatus('failed'));
|
||||
dispatch(
|
||||
conversationSlice.actions.raiseError({
|
||||
conversationId: currentConversationId,
|
||||
index: targetIndex,
|
||||
message: data.error,
|
||||
}),
|
||||
@@ -134,87 +138,88 @@ export const fetchAnswer = createAsyncThunk<
|
||||
} else {
|
||||
dispatch(
|
||||
updateStreamingQuery({
|
||||
conversationId: currentConversationId,
|
||||
index: targetIndex,
|
||||
query: { response: data.answer },
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
indx,
|
||||
state.preference.selectedAgent?.id,
|
||||
attachmentIds,
|
||||
true, // Always save conversation
|
||||
);
|
||||
} else {
|
||||
const answer = await handleFetchAnswer(
|
||||
question,
|
||||
signal,
|
||||
state.preference.token,
|
||||
state.preference.selectedDocs!,
|
||||
state.conversation.queries,
|
||||
state.conversation.conversationId,
|
||||
state.preference.prompt.id,
|
||||
state.preference.chunks,
|
||||
state.preference.token_limit,
|
||||
state.preference.selectedAgent?.id,
|
||||
attachmentIds,
|
||||
true, // Always save conversation
|
||||
);
|
||||
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;
|
||||
});
|
||||
|
||||
const targetIndex = indx ?? state.conversation.queries.length - 1;
|
||||
|
||||
dispatch(
|
||||
updateQuery({
|
||||
index: targetIndex,
|
||||
query: {
|
||||
response: answer.answer,
|
||||
thought: answer.thought,
|
||||
sources: sourcesPrepped,
|
||||
tool_calls: answer.toolCalls,
|
||||
},
|
||||
}),
|
||||
},
|
||||
indx,
|
||||
state.preference.selectedAgent?.id,
|
||||
attachmentIds,
|
||||
save_conversation,
|
||||
);
|
||||
dispatch(
|
||||
updateConversationId({
|
||||
query: { conversationId: answer.conversationId },
|
||||
}),
|
||||
} else {
|
||||
const answer = await handleFetchAnswer(
|
||||
question,
|
||||
signal,
|
||||
state.preference.token,
|
||||
state.preference.selectedDocs!,
|
||||
state.conversation.queries,
|
||||
state.conversation.conversationId,
|
||||
state.preference.prompt.id,
|
||||
state.preference.chunks,
|
||||
state.preference.token_limit,
|
||||
state.preference.selectedAgent?.id,
|
||||
attachmentIds,
|
||||
save_conversation,
|
||||
);
|
||||
getConversations(state.preference.token)
|
||||
.then((fetchedConversations) => {
|
||||
dispatch(setConversations(fetchedConversations));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to fetch conversations: ', error);
|
||||
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(conversationSlice.actions.setStatus('idle'));
|
||||
|
||||
const targetIndex = indx ?? state.conversation.queries.length - 1;
|
||||
|
||||
dispatch(
|
||||
updateQuery({
|
||||
index: targetIndex,
|
||||
query: {
|
||||
response: answer.answer,
|
||||
thought: answer.thought,
|
||||
sources: sourcesPrepped,
|
||||
tool_calls: answer.toolCalls,
|
||||
},
|
||||
}),
|
||||
);
|
||||
if (!isPreview) {
|
||||
dispatch(
|
||||
updateConversationId({
|
||||
query: { conversationId: answer.conversationId },
|
||||
}),
|
||||
);
|
||||
getConversations(state.preference.token)
|
||||
.then((fetchedConversations) => {
|
||||
dispatch(setConversations(fetchedConversations));
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to fetch conversations: ', error);
|
||||
});
|
||||
}
|
||||
dispatch(conversationSlice.actions.setStatus('idle'));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
conversationId: null,
|
||||
title: null,
|
||||
answer: '',
|
||||
query: question,
|
||||
result: '',
|
||||
thought: '',
|
||||
sources: [],
|
||||
tool_calls: [],
|
||||
};
|
||||
});
|
||||
return {
|
||||
conversationId: null,
|
||||
title: null,
|
||||
answer: '',
|
||||
query: question,
|
||||
result: '',
|
||||
thought: '',
|
||||
sources: [],
|
||||
tool_calls: [],
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
export const conversationSlice = createSlice({
|
||||
name: 'conversation',
|
||||
@@ -237,20 +242,18 @@ export const conversationSlice = createSlice({
|
||||
},
|
||||
updateStreamingQuery(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
conversationId: string | null;
|
||||
index: number;
|
||||
query: Partial<Query>;
|
||||
}>,
|
||||
action: PayloadAction<{ index: number; query: Partial<Query> }>,
|
||||
) {
|
||||
const { conversationId, index, query } = action.payload;
|
||||
// Only update if this update is for the current conversation
|
||||
if (state.status === 'idle' || state.conversationId !== conversationId)
|
||||
return;
|
||||
|
||||
if (state.status === 'idle') return;
|
||||
const { index, query } = action.payload;
|
||||
if (query.response != undefined) {
|
||||
state.queries[index].response =
|
||||
(state.queries[index].response || '') + query.response;
|
||||
} else {
|
||||
state.queries[index] = {
|
||||
...state.queries[index],
|
||||
...query,
|
||||
};
|
||||
}
|
||||
},
|
||||
updateConversationId(
|
||||
@@ -262,31 +265,29 @@ export const conversationSlice = createSlice({
|
||||
},
|
||||
updateThought(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
conversationId: string | null;
|
||||
index: number;
|
||||
query: Partial<Query>;
|
||||
}>,
|
||||
action: PayloadAction<{ index: number; query: Partial<Query> }>,
|
||||
) {
|
||||
const { conversationId, index, query } = action.payload;
|
||||
if (state.conversationId !== conversationId) return;
|
||||
|
||||
const { index, query } = action.payload;
|
||||
if (query.thought != undefined) {
|
||||
state.queries[index].thought =
|
||||
(state.queries[index].thought || '') + query.thought;
|
||||
} else {
|
||||
state.queries[index] = {
|
||||
...state.queries[index],
|
||||
...query,
|
||||
};
|
||||
}
|
||||
},
|
||||
updateStreamingSource(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
conversationId: string | null;
|
||||
index: number;
|
||||
query: Partial<Query>;
|
||||
}>,
|
||||
action: PayloadAction<{ index: number; query: Partial<Query> }>,
|
||||
) {
|
||||
const { index, query } = action.payload;
|
||||
if (query.sources !== undefined)
|
||||
state.queries[index].sources = query.sources;
|
||||
if (!state.queries[index].sources) {
|
||||
state.queries[index].sources = query?.sources;
|
||||
} else {
|
||||
state.queries[index].sources!.push(query.sources![0]);
|
||||
}
|
||||
},
|
||||
updateToolCall(state, action) {
|
||||
const { index, tool_call } = action.payload;
|
||||
@@ -322,15 +323,9 @@ export const conversationSlice = createSlice({
|
||||
},
|
||||
raiseError(
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
conversationId: string | null;
|
||||
index: number;
|
||||
message: string;
|
||||
}>,
|
||||
action: PayloadAction<{ index: number; message: string }>,
|
||||
) {
|
||||
const { conversationId, index, message } = action.payload;
|
||||
if (state.conversationId !== conversationId) return;
|
||||
|
||||
const { index, message } = action.payload;
|
||||
state.queries[index].error = message;
|
||||
},
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, RefObject, useState } from 'react';
|
||||
|
||||
export function useOutsideAlerter<T extends HTMLElement>(
|
||||
ref: RefObject<T | null>,
|
||||
ref: RefObject<T>,
|
||||
handler: () => void,
|
||||
additionalDeps: unknown[],
|
||||
handleEscapeKey?: boolean,
|
||||
@@ -30,7 +30,7 @@ export function useOutsideAlerter<T extends HTMLElement>(
|
||||
document.removeEventListener('keydown', handleEscape);
|
||||
}
|
||||
};
|
||||
}, [ref, handler, handleEscapeKey, ...additionalDeps]);
|
||||
}, [ref, ...additionalDeps]);
|
||||
}
|
||||
|
||||
export function useMediaQuery() {
|
||||
|
||||
@@ -1,169 +1,107 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap')
|
||||
layer(base);
|
||||
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&display=swap');
|
||||
|
||||
@import 'tailwindcss';
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
@theme {
|
||||
--font-roboto: Roboto, sans-serif;
|
||||
|
||||
--color-eerie-black: #212121;
|
||||
--color-black-1000: #343541;
|
||||
--color-jet: #343541;
|
||||
--color-gray-alpha: rgba(0, 0, 0, 0.64);
|
||||
--color-gray-1000: #f6f6f6;
|
||||
--color-gray-2000: rgba(0, 0, 0, 0.5);
|
||||
--color-gray-3000: rgba(243, 243, 243, 1);
|
||||
--color-gray-4000: #949494;
|
||||
--color-gray-5000: #bbbbbb;
|
||||
--color-gray-6000: #757575;
|
||||
--color-red-1000: rgb(254, 202, 202);
|
||||
--color-red-2000: #f44336;
|
||||
--color-red-3000: #621b16;
|
||||
--color-blue-1000: #7d54d1;
|
||||
--color-blue-2000: #002b49;
|
||||
--color-blue-3000: #4b02e2;
|
||||
--color-purple-30: #7d54d1;
|
||||
--color-purple-3000: rgb(230, 222, 247);
|
||||
--color-blue-4000: rgba(0, 125, 255, 0.36);
|
||||
--color-blue-5000: rgba(0, 125, 255);
|
||||
--color-green-2000: #0fff50;
|
||||
--color-light-gray: #edeef0;
|
||||
--color-white-3000: #ffffff;
|
||||
--color-just-black: #00000;
|
||||
--color-purple-taupe: #464152;
|
||||
--color-dove-gray: #6c6c6c;
|
||||
--color-silver: #c4c4c4;
|
||||
--color-rainy-gray: #a4a4a4;
|
||||
--color-raisin-black: #222327;
|
||||
--color-chinese-black: #161616;
|
||||
--color-chinese-silver: #cdcdcd;
|
||||
--color-dark-charcoal: #2f3036;
|
||||
--color-bright-gray: #ebebeb;
|
||||
--color-outer-space: #444654;
|
||||
--color-gun-metal: #2e303e;
|
||||
--color-sonic-silver: #747474;
|
||||
--color-soap: #d8ccf1;
|
||||
--color-independence: #54546d;
|
||||
--color-philippine-yellow: #ffc700;
|
||||
--color-chinese-white: #e0e0e0;
|
||||
--color-dark-gray: #aaaaaa;
|
||||
--color-dim-gray: #6a6a6a;
|
||||
--color-cultured: #f4f4f4;
|
||||
--color-charleston-green: #2b2c31;
|
||||
--color-charleston-green-2: #26272e;
|
||||
--color-charleston-green-3: #26272a;
|
||||
--color-grey: #7e7e7e;
|
||||
--color-lotion: #fbfbfb;
|
||||
--color-platinum: #e6e6e6;
|
||||
--color-eerie-black-2: #191919;
|
||||
--color-light-silver: #d9d9d9;
|
||||
--color-carbon: #2e2e2e;
|
||||
--color-onyx: #35363b;
|
||||
--color-royal-purple: #6c4ab0;
|
||||
--color-chinese-black-2: #0f1419;
|
||||
--color-gainsboro: #d9dcde;
|
||||
--color-onyx-2: #35383c;
|
||||
--color-philippine-grey: #929292;
|
||||
--color-charcoal-grey: #53545d;
|
||||
--color-rosso-corsa: #d30000;
|
||||
--color-north-texas-green: #0c9d35;
|
||||
--color-medium-purple: #8d66dd;
|
||||
--color-slate-blue: #6f5fca;
|
||||
--color-old-silver: #848484;
|
||||
--color-arsenic: #4d4e58;
|
||||
--color-light-gainsboro: #d7d7d7;
|
||||
--color-raisin-black-light: #18181b;
|
||||
--color-gunmetal: #32333b;
|
||||
--color-sonic-silver-light: #7f7f82;
|
||||
--color-violets-are-blue: #976af3;
|
||||
:root {
|
||||
--viewport-height: 100vh;
|
||||
font-synthesis: none !important;
|
||||
}
|
||||
|
||||
/*
|
||||
The default border color has changed to `currentcolor` in Tailwind CSS v4,
|
||||
so we've added these compatibility styles to make sure everything still
|
||||
looks the same as it did with Tailwind CSS v3.
|
||||
|
||||
If we ever want to remove these styles, we need to add an explicit border
|
||||
color utility to any element that depends on these defaults.
|
||||
*/
|
||||
@layer base {
|
||||
*,
|
||||
::after,
|
||||
::before,
|
||||
::backdrop,
|
||||
::file-selector-button {
|
||||
border-color: var(--color-gray-200, currentcolor);
|
||||
@supports (height: 100dvh) {
|
||||
:root {
|
||||
--viewport-height: 100dvh; /* Use dvh where supported */
|
||||
}
|
||||
}
|
||||
|
||||
@utility no-scrollbar {
|
||||
body.dark {
|
||||
background-color: #202124; /* raisin-black */
|
||||
}
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
.dark ::-webkit-scrollbar-track {
|
||||
background: #2f3036;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 40px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
.dark ::-webkit-scrollbar-thumb:hover {
|
||||
background: #b1afaf;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
/* Chrome, Safari and Opera */
|
||||
&::-webkit-scrollbar {
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
@utility scrollbar-thin {
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
|
||||
/* Thin scrollbar utility */
|
||||
&::-webkit-scrollbar {
|
||||
.scrollbar-thin::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-track {
|
||||
.scrollbar-thin::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb {
|
||||
.scrollbar-thin::-webkit-scrollbar-thumb {
|
||||
background: rgba(156, 163, 175, 0.5);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar-thumb:hover {
|
||||
.scrollbar-thin::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(156, 163, 175, 0.7);
|
||||
}
|
||||
|
||||
/* For Firefox */
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
|
||||
.scrollbar-thin {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(156, 163, 175, 0.5) transparent;
|
||||
}
|
||||
}
|
||||
|
||||
@utility table-default {
|
||||
@apply block w-full table-auto justify-center overflow-auto rounded-xl border border-silver text-center dark:border-silver/40 dark:text-bright-gray;
|
||||
@layer components {
|
||||
.table-default {
|
||||
@apply block w-full table-auto justify-center overflow-auto rounded-xl border border-silver text-center dark:border-silver/40 dark:text-bright-gray;
|
||||
}
|
||||
|
||||
& th {
|
||||
.table-default th {
|
||||
@apply text-nowrap p-4 font-normal text-gray-400;
|
||||
}
|
||||
|
||||
& th {
|
||||
.table-default th {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
& th:last-child {
|
||||
.table-default th:last-child {
|
||||
flex: 0;
|
||||
}
|
||||
|
||||
& td {
|
||||
.table-default td {
|
||||
@apply w-full border-t border-silver px-4 py-2 dark:border-silver/40;
|
||||
}
|
||||
|
||||
& td:last-child {
|
||||
.table-default td:last-child {
|
||||
@apply border-r-0;
|
||||
}
|
||||
|
||||
& th {
|
||||
min-width: 150px;
|
||||
max-width: 320px;
|
||||
overflow: auto;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: grey transparent;
|
||||
}
|
||||
|
||||
& td {
|
||||
.table-default th,
|
||||
.table-default td {
|
||||
min-width: 150px;
|
||||
max-width: 320px;
|
||||
overflow: auto;
|
||||
@@ -172,44 +110,7 @@ layer(base);
|
||||
}
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
:root {
|
||||
--viewport-height: 100vh;
|
||||
font-synthesis: none !important;
|
||||
}
|
||||
|
||||
@supports (height: 100dvh) {
|
||||
:root {
|
||||
--viewport-height: 100dvh; /* Use dvh where supported */
|
||||
}
|
||||
}
|
||||
|
||||
body.dark {
|
||||
background-color: #202124; /* raisin-black */
|
||||
}
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
}
|
||||
::-webkit-scrollbar-track {
|
||||
background: #f1f1f1;
|
||||
}
|
||||
.dark ::-webkit-scrollbar-track {
|
||||
background: #2f3036;
|
||||
}
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: #888;
|
||||
border-radius: 40px;
|
||||
}
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: #555;
|
||||
}
|
||||
.dark ::-webkit-scrollbar-thumb:hover {
|
||||
background: #b1afaf;
|
||||
}
|
||||
}
|
||||
|
||||
@layer base{
|
||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||
|
||||
/* Document
|
||||
========================================================================== */
|
||||
@@ -425,7 +326,6 @@ button,
|
||||
[type='reset'],
|
||||
[type='submit'] {
|
||||
-webkit-appearance: button;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -686,5 +586,3 @@ input:-webkit-autofill:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -104,7 +104,7 @@ export default function AddToolModal({
|
||||
>
|
||||
<div className="flex h-full flex-col">
|
||||
<div>
|
||||
<h2 className="text-jet dark:text-bright-gray px-3 text-xl font-semibold">
|
||||
<h2 className="px-3 text-xl font-semibold text-jet dark:text-bright-gray">
|
||||
{t('settings.tools.selectToolSetup')}
|
||||
</h2>
|
||||
<div className="mt-5 h-[73vh] overflow-auto px-3 py-px">
|
||||
@@ -119,7 +119,7 @@ export default function AddToolModal({
|
||||
role="button"
|
||||
tabIndex={0}
|
||||
key={index}
|
||||
className="border-light-gainsboro bg-white-3000 dark:border-arsenic dark:bg-gunmetal flex h-52 w-full cursor-pointer flex-col justify-between rounded-2xl border p-6 hover:border-[#9d9d9d] dark:hover:border-[#717179]"
|
||||
className="flex h-52 w-full cursor-pointer flex-col justify-between rounded-2xl border border-light-gainsboro bg-white-3000 p-6 hover:border-[#9d9d9d] dark:border-arsenic dark:bg-gunmetal hover:dark:border-[#717179]"
|
||||
onClick={() => {
|
||||
setSelectedTool(tool);
|
||||
handleAddTool(tool);
|
||||
@@ -142,11 +142,11 @@ export default function AddToolModal({
|
||||
<div className="mt-[9px]">
|
||||
<p
|
||||
title={tool.displayName}
|
||||
className="text-raisin-black-light dark:text-bright-gray truncate px-1 text-[13px] leading-relaxed font-semibold capitalize"
|
||||
className="truncate px-1 text-[13px] font-semibold capitalize leading-relaxed text-raisin-black-light dark:text-bright-gray"
|
||||
>
|
||||
{tool.displayName}
|
||||
</p>
|
||||
<p className="text-old-silver dark:text-sonic-silver-light mt-1 h-24 overflow-auto px-1 text-[12px] leading-relaxed">
|
||||
<p className="mt-1 h-24 overflow-auto px-1 text-[12px] leading-relaxed text-old-silver dark:text-sonic-silver-light">
|
||||
{tool.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -33,24 +33,11 @@ export default function ChunkModal({
|
||||
setChunkText(originalText || '');
|
||||
}, [originalTitle, originalText]);
|
||||
|
||||
const resetForm = () => {
|
||||
setTitle('');
|
||||
setChunkText('');
|
||||
};
|
||||
|
||||
const handleDeleteConfirmed = () => {
|
||||
if (handleDelete) {
|
||||
handleDelete();
|
||||
}
|
||||
setDeleteModal('INACTIVE');
|
||||
setModalState('INACTIVE');
|
||||
};
|
||||
|
||||
if (modalState !== 'ACTIVE') return null;
|
||||
|
||||
const content = (
|
||||
<div>
|
||||
<h2 className="text-jet dark:text-bright-gray px-3 text-xl font-semibold">
|
||||
<h2 className="px-3 text-xl font-semibold text-jet dark:text-bright-gray">
|
||||
{t(`modals.chunk.${type === 'ADD' ? 'add' : 'edit'}`)}
|
||||
</h2>
|
||||
<div className="relative mt-6 px-3">
|
||||
@@ -64,13 +51,13 @@ export default function ChunkModal({
|
||||
/>
|
||||
</div>
|
||||
<div className="relative mt-6 px-3">
|
||||
<div className="border-silver dark:border-silver/40 rounded-lg border pt-3 pb-1">
|
||||
<span className="text-gray-4000 dark:text-silver absolute -top-2 left-5 rounded-lg bg-white px-2 text-xs dark:bg-[#26272E]">
|
||||
<div className="rounded-lg border border-silver pb-1 pt-3 dark:border-silver/40">
|
||||
<span className="absolute -top-2 left-5 rounded-lg bg-white px-2 text-xs text-gray-4000 dark:bg-[#26272E] dark:text-silver">
|
||||
{t('modals.chunk.bodyText')}
|
||||
</span>
|
||||
<textarea
|
||||
id="chunk-body-text"
|
||||
className="h-60 max-h-60 w-full resize-none px-3 outline-hidden dark:bg-transparent dark:text-white"
|
||||
className="h-60 max-h-60 w-full resize-none px-3 outline-none dark:bg-transparent dark:text-white"
|
||||
value={chunkText}
|
||||
onChange={(e) => setChunkText(e.target.value)}
|
||||
aria-label={t('modals.chunk.promptText')}
|
||||
@@ -84,18 +71,16 @@ export default function ChunkModal({
|
||||
onClick={() => {
|
||||
handleSubmit(title, chunkText);
|
||||
setModalState('INACTIVE');
|
||||
resetForm();
|
||||
}}
|
||||
className="bg-purple-30 hover:bg-violets-are-blue rounded-3xl px-5 py-2 text-sm text-white transition-all"
|
||||
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-violets-are-blue"
|
||||
>
|
||||
{t('modals.chunk.add')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setModalState('INACTIVE');
|
||||
resetForm();
|
||||
}}
|
||||
className="dark:text-light-gray cursor-pointer rounded-3xl px-5 py-2 text-sm font-medium hover:bg-gray-100 dark:bg-transparent dark:hover:bg-[#767183]/50"
|
||||
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"
|
||||
>
|
||||
{t('modals.chunk.close')}
|
||||
</button>
|
||||
@@ -103,7 +88,7 @@ export default function ChunkModal({
|
||||
) : (
|
||||
<div className="mt-8 flex w-full items-center justify-between px-3">
|
||||
<button
|
||||
className="rounded-full border border-solid border-red-500 px-5 py-2 text-sm text-nowrap text-red-500 hover:bg-red-500 hover:text-white"
|
||||
className="text-nowrap rounded-full border border-solid border-red-500 px-5 py-2 text-sm text-red-500 hover:bg-red-500 hover:text-white"
|
||||
onClick={() => {
|
||||
setDeleteModal('ACTIVE');
|
||||
}}
|
||||
@@ -116,7 +101,7 @@ export default function ChunkModal({
|
||||
handleSubmit(title, chunkText);
|
||||
setModalState('INACTIVE');
|
||||
}}
|
||||
className="bg-purple-30 hover:bg-violets-are-blue rounded-3xl px-5 py-2 text-sm text-white transition-all"
|
||||
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-violets-are-blue"
|
||||
>
|
||||
{t('modals.chunk.update')}
|
||||
</button>
|
||||
@@ -124,7 +109,7 @@ export default function ChunkModal({
|
||||
onClick={() => {
|
||||
setModalState('INACTIVE');
|
||||
}}
|
||||
className="dark:text-light-gray cursor-pointer rounded-3xl px-5 py-2 text-sm font-medium hover:bg-gray-100 dark:bg-transparent dark:hover:bg-[#767183]/50"
|
||||
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"
|
||||
>
|
||||
{t('modals.chunk.close')}
|
||||
</button>
|
||||
@@ -139,7 +124,6 @@ export default function ChunkModal({
|
||||
<WrapperModal
|
||||
close={() => setModalState('INACTIVE')}
|
||||
className="sm:w-[620px]"
|
||||
isPerformingTask={true}
|
||||
>
|
||||
{content}
|
||||
</WrapperModal>
|
||||
@@ -149,7 +133,13 @@ export default function ChunkModal({
|
||||
message={t('modals.chunk.deleteConfirmation')}
|
||||
modalState={deleteModal}
|
||||
setModalState={setDeleteModal}
|
||||
handleSubmit={handleDeleteConfirmed}
|
||||
handleSubmit={
|
||||
handleDelete
|
||||
? handleDelete
|
||||
: () => {
|
||||
/* no-op */
|
||||
}
|
||||
}
|
||||
submitLabel={t('modals.chunk.delete')}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -29,40 +29,33 @@ export default function ConfirmationModal({
|
||||
? 'rounded-3xl bg-rosso-corsa px-5 py-2 text-sm text-lotion transition-all hover:bg-red-2000 hover:font-bold tracking-[0.019em] hover:tracking-normal'
|
||||
: 'rounded-3xl bg-purple-30 px-5 py-2 text-sm text-lotion transition-all hover:bg-violets-are-blue';
|
||||
|
||||
const handleSubmitClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
handleSubmit();
|
||||
setModalState('INACTIVE');
|
||||
};
|
||||
|
||||
const handleCancelClick = (e: React.MouseEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setModalState('INACTIVE');
|
||||
handleCancel?.();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{modalState === 'ACTIVE' && (
|
||||
<WrapperModal close={() => setModalState('INACTIVE')}>
|
||||
<div className="relative">
|
||||
<div>
|
||||
<p className="font-base text-jet dark:text-bright-gray mb-1 w-[90%] text-lg break-words">
|
||||
<p className="font-base mb-1 w-[90%] break-words text-lg text-jet dark:text-bright-gray">
|
||||
{message}
|
||||
</p>
|
||||
<div>
|
||||
<div className="mt-6 flex flex-row-reverse gap-1">
|
||||
<button
|
||||
onClick={handleSubmitClick}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleSubmit();
|
||||
}}
|
||||
className={submitButtonClasses}
|
||||
>
|
||||
{submitLabel}
|
||||
</button>
|
||||
<button
|
||||
onClick={handleCancelClick}
|
||||
className="dark:text-light-gray cursor-pointer rounded-3xl px-5 py-2 text-sm font-medium hover:bg-gray-100 dark:bg-transparent dark:hover:bg-[#767183]/50"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
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>
|
||||
|
||||
@@ -3,33 +3,38 @@ import { createPortal } from 'react-dom';
|
||||
|
||||
import Exit from '../assets/exit.svg';
|
||||
|
||||
type WrapperModalPropsType = {
|
||||
interface WrapperModalPropsType {
|
||||
children: React.ReactNode;
|
||||
close: () => void;
|
||||
isPerformingTask?: boolean;
|
||||
className?: string;
|
||||
contentClassName?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export default function WrapperModal({
|
||||
children,
|
||||
close,
|
||||
isPerformingTask = false,
|
||||
className = '',
|
||||
contentClassName = '',
|
||||
className = '', // Default width, but can be overridden
|
||||
contentClassName = '', // Default padding, but can be overridden
|
||||
}: WrapperModalPropsType) {
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isPerformingTask) return;
|
||||
|
||||
const handleClickOutside = (event: MouseEvent) => {
|
||||
if (modalRef.current && !modalRef.current.contains(event.target as Node))
|
||||
if (
|
||||
modalRef.current &&
|
||||
!modalRef.current.contains(event.target as Node)
|
||||
) {
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
const handleEscapePress = (event: KeyboardEvent) => {
|
||||
if (event.key === 'Escape') close();
|
||||
if (event.key === 'Escape') {
|
||||
close();
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
@@ -39,17 +44,17 @@ export default function WrapperModal({
|
||||
document.removeEventListener('mousedown', handleClickOutside);
|
||||
document.removeEventListener('keydown', handleEscapePress);
|
||||
};
|
||||
}, [close, isPerformingTask]);
|
||||
}, [close]);
|
||||
|
||||
const modalContent = (
|
||||
<div className="bg-gray-alpha bg-opacity-50 fixed top-0 left-0 z-30 flex h-screen w-screen items-center justify-center">
|
||||
<div className="fixed left-0 top-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-8 sm:w-[512px] dark:bg-[#26272E] ${className}`}
|
||||
className={`relative w-11/12 rounded-2xl bg-white p-8 dark:bg-[#26272E] sm:w-[512px] ${className}`}
|
||||
>
|
||||
{!isPerformingTask && (
|
||||
<button
|
||||
className="absolute top-3 right-4 z-50 m-2 w-3"
|
||||
className="absolute right-4 top-3 z-50 m-2 w-3"
|
||||
onClick={close}
|
||||
>
|
||||
<img className="filter dark:invert" src={Exit} alt="Close" />
|
||||
|
||||
@@ -25,10 +25,10 @@ function AddPrompt({
|
||||
|
||||
return (
|
||||
<div>
|
||||
<p className="text-jet dark:text-bright-gray mb-1 text-xl">
|
||||
<p className="mb-1 text-xl text-jet dark:text-bright-gray">
|
||||
{t('modals.prompts.addPrompt')}
|
||||
</p>
|
||||
<p className="text-sonic-silver mb-7 text-xs dark:text-[#7F7F82]">
|
||||
<p className="mb-7 text-xs text-[#747474] dark:text-[#7F7F82]">
|
||||
{t('modals.prompts.addDescription')}
|
||||
</p>
|
||||
<div>
|
||||
@@ -41,8 +41,8 @@ function AddPrompt({
|
||||
labelBgClassName="bg-white dark:bg-[#26272E]"
|
||||
borderVariant="thin"
|
||||
/>
|
||||
<div className="relative top-[7px] left-3">
|
||||
<span className="text-silver dark:text-silver bg-white px-1 text-xs dark:bg-[#26272E]">
|
||||
<div className="relative left-3 top-[7px]">
|
||||
<span className="bg-white px-1 text-xs text-silver dark:bg-[#26272E] dark:text-silver">
|
||||
{t('modals.prompts.promptText')}
|
||||
</span>
|
||||
</div>
|
||||
@@ -51,7 +51,7 @@ function AddPrompt({
|
||||
</label>
|
||||
<textarea
|
||||
id="new-prompt-content"
|
||||
className="border-silver dark:border-silver/40 h-56 w-full resize-none rounded-lg border-2 px-3 py-2 outline-hidden dark:bg-transparent dark:text-white"
|
||||
className="h-56 w-full resize-none rounded-lg border-2 border-silver px-3 py-2 outline-none dark:border-silver/40 dark:bg-transparent dark:text-white"
|
||||
value={newPromptContent}
|
||||
onChange={(e) => setNewPromptContent(e.target.value)}
|
||||
aria-label="Prompt Text"
|
||||
@@ -60,7 +60,7 @@ function AddPrompt({
|
||||
<div className="mt-6 flex flex-row-reverse">
|
||||
<button
|
||||
onClick={handleAddPrompt}
|
||||
className="bg-purple-30 hover:bg-violets-are-blue disabled:hover:bg-purple-30 rounded-3xl px-5 py-2 text-sm text-white transition-all"
|
||||
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-violets-are-blue disabled:hover:bg-purple-30"
|
||||
disabled={disableSave}
|
||||
title={
|
||||
disableSave && newPromptName ? t('modals.prompts.nameExists') : ''
|
||||
@@ -97,10 +97,10 @@ function EditPrompt({
|
||||
return (
|
||||
<div>
|
||||
<div className="">
|
||||
<p className="text-jet dark:text-bright-gray mb-1 text-xl">
|
||||
<p className="mb-1 text-xl text-jet dark:text-bright-gray">
|
||||
{t('modals.prompts.editPrompt')}
|
||||
</p>
|
||||
<p className="text-sonic-silver mb-7 text-xs dark:text-[#7F7F82]">
|
||||
<p className="mb-7 text-xs text-[#747474] dark:text-[#7F7F82]">
|
||||
{t('modals.prompts.editDescription')}
|
||||
</p>
|
||||
<div>
|
||||
@@ -113,8 +113,8 @@ function EditPrompt({
|
||||
labelBgClassName="bg-white dark:bg-charleston-green-2"
|
||||
borderVariant="thin"
|
||||
/>
|
||||
<div className="relative top-[7px] left-3">
|
||||
<span className="text-silver dark:bg-charleston-green-2 dark:text-silver bg-white px-1 text-xs">
|
||||
<div className="relative left-3 top-[7px]">
|
||||
<span className="bg-white px-1 text-xs text-silver dark:bg-charleston-green-2 dark:text-silver">
|
||||
{t('modals.prompts.promptText')}
|
||||
</span>
|
||||
</div>
|
||||
@@ -123,7 +123,7 @@ function EditPrompt({
|
||||
</label>
|
||||
<textarea
|
||||
id="edit-prompt-content"
|
||||
className="border-silver dark:border-silver/40 h-56 w-full resize-none rounded-lg border-2 px-3 py-2 outline-hidden dark:bg-transparent dark:text-white"
|
||||
className="h-56 w-full resize-none rounded-lg border-2 border-silver px-3 py-2 outline-none dark:border-silver/40 dark:bg-transparent dark:text-white"
|
||||
value={editPromptContent}
|
||||
onChange={(e) => setEditPromptContent(e.target.value)}
|
||||
aria-label="Prompt Text"
|
||||
@@ -131,7 +131,7 @@ function EditPrompt({
|
||||
</div>
|
||||
<div className="mt-6 flex flex-row-reverse gap-4">
|
||||
<button
|
||||
className={`bg-purple-30 hover:bg-violets-are-blue disabled:hover:bg-purple-30 rounded-3xl px-5 py-2 text-sm text-white transition-all ${
|
||||
className={`rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-violets-are-blue disabled:hover:bg-purple-30 ${
|
||||
currentPromptEdit.type === 'public'
|
||||
? 'cursor-not-allowed opacity-50'
|
||||
: ''
|
||||
|
||||
@@ -102,9 +102,9 @@ export default function APIKeys() {
|
||||
|
||||
return (
|
||||
<div className="mt-8 flex w-full max-w-full flex-col overflow-hidden">
|
||||
<div className="relative flex grow flex-col">
|
||||
<div className="relative flex flex-grow flex-col">
|
||||
<div className="mb-6">
|
||||
<h2 className="text-sonic-silver text-base font-medium">
|
||||
<h2 className="text-base font-medium text-sonic-silver">
|
||||
{t('settings.apiKeys.description')}
|
||||
</h2>
|
||||
</div>
|
||||
@@ -112,7 +112,7 @@ export default function APIKeys() {
|
||||
<div className="mb-6 flex flex-col items-start justify-end gap-3 sm:flex-row sm:items-center">
|
||||
<button
|
||||
onClick={() => setCreateModal(true)}
|
||||
className="bg-purple-30 hover:bg-violets-are-blue flex h-[30px] w-[108px] items-center justify-center rounded-full text-sm text-white"
|
||||
className="flex h-[30px] w-[108px] items-center justify-center rounded-full bg-purple-30 text-sm text-white hover:bg-violets-are-blue"
|
||||
title={t('settings.apiKeys.createNew')}
|
||||
>
|
||||
{t('settings.apiKeys.createNew')}
|
||||
@@ -120,18 +120,18 @@ export default function APIKeys() {
|
||||
</div>
|
||||
|
||||
<div className="relative w-full">
|
||||
<div className="dark:border-silver/40 overflow-hidden rounded-md border border-gray-300">
|
||||
<div className="overflow-hidden rounded-md border border-gray-300 dark:border-silver/40">
|
||||
<div className="table-scroll overflow-x-auto">
|
||||
<table className="w-full table-auto">
|
||||
<thead>
|
||||
<tr className="dark:border-silver/40 border-b border-gray-300">
|
||||
<th className="text-sonic-silver w-[35%] px-4 py-3 text-left text-xs font-medium">
|
||||
<tr className="border-b border-gray-300 dark:border-silver/40">
|
||||
<th className="w-[35%] px-4 py-3 text-left text-xs font-medium text-sonic-silver">
|
||||
{t('settings.apiKeys.name')}
|
||||
</th>
|
||||
<th className="text-sonic-silver w-[35%] px-4 py-3 text-left text-xs font-medium">
|
||||
<th className="w-[35%] px-4 py-3 text-left text-xs font-medium text-sonic-silver">
|
||||
{t('settings.apiKeys.sourceDoc')}
|
||||
</th>
|
||||
<th className="text-sonic-silver w-[25%] px-4 py-3 text-left text-xs font-medium">
|
||||
<th className="w-[25%] px-4 py-3 text-left text-xs font-medium text-sonic-silver">
|
||||
<span className="hidden sm:inline">
|
||||
{t('settings.apiKeys.key')}
|
||||
</span>
|
||||
@@ -144,7 +144,7 @@ export default function APIKeys() {
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="dark:divide-silver/40 divide-y divide-gray-300">
|
||||
<tbody className="divide-y divide-gray-300 dark:divide-silver/40">
|
||||
{loading ? (
|
||||
<SkeletonLoader component="table" />
|
||||
) : !apiKeys?.length ? (
|
||||
@@ -163,12 +163,12 @@ export default function APIKeys() {
|
||||
key={element.id}
|
||||
className="group transition-colors hover:bg-gray-50 dark:hover:bg-gray-800/50"
|
||||
>
|
||||
<td className="w-[35%] max-w-0 min-w-48 px-4 py-4 text-sm font-semibold text-gray-700 dark:text-[#E0E0E0]">
|
||||
<td className="w-[35%] min-w-48 max-w-0 px-4 py-4 text-sm font-semibold text-gray-700 dark:text-[#E0E0E0]">
|
||||
<div className="truncate" title={element.name}>
|
||||
{element.name}
|
||||
</div>
|
||||
</td>
|
||||
<td className="w-[35%] max-w-0 min-w-48 px-4 py-4 text-sm text-gray-700 dark:text-[#E0E0E0]">
|
||||
<td className="w-[35%] min-w-48 max-w-0 px-4 py-4 text-sm text-gray-700 dark:text-[#E0E0E0]">
|
||||
<div className="truncate" title={element.source}>
|
||||
{element.source}
|
||||
</div>
|
||||
@@ -187,7 +187,7 @@ export default function APIKeys() {
|
||||
name: element.name,
|
||||
})
|
||||
}
|
||||
className="inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full transition-colors hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
className="inline-flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full transition-colors hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
>
|
||||
<img
|
||||
src={Trash}
|
||||
|
||||
@@ -9,10 +9,8 @@ import Edit from '../assets/edit.svg';
|
||||
import EyeView from '../assets/eye-view.svg';
|
||||
import NoFilesDarkIcon from '../assets/no-files-dark.svg';
|
||||
import NoFilesIcon from '../assets/no-files.svg';
|
||||
import Trash from '../assets/red-trash.svg';
|
||||
import SyncIcon from '../assets/sync.svg';
|
||||
import ThreeDots from '../assets/three-dots.svg';
|
||||
import ContextMenu, { MenuOption } from '../components/ContextMenu';
|
||||
import Trash from '../assets/red-trash.svg';
|
||||
import Pagination from '../components/DocumentPagination';
|
||||
import DropdownMenu from '../components/DropdownMenu';
|
||||
import Input from '../components/Input';
|
||||
@@ -31,6 +29,8 @@ import {
|
||||
import Upload from '../upload/Upload';
|
||||
import { formatDate } from '../utils/dateTimeUtils';
|
||||
import { ChunkType } from './types';
|
||||
import ContextMenu, { MenuOption } from '../components/ContextMenu';
|
||||
import ThreeDots from '../assets/three-dots.svg';
|
||||
|
||||
const formatTokens = (tokens: number): string => {
|
||||
const roundToTwoDecimals = (num: number): string => {
|
||||
@@ -68,9 +68,9 @@ export default function Documents({
|
||||
const [totalPages, setTotalPages] = useState<number>(1);
|
||||
|
||||
const [activeMenuId, setActiveMenuId] = useState<string | null>(null);
|
||||
const menuRefs = useRef<{
|
||||
[key: string]: React.RefObject<HTMLDivElement | null>;
|
||||
}>({});
|
||||
const menuRefs = useRef<{ [key: string]: React.RefObject<HTMLDivElement> }>(
|
||||
{},
|
||||
);
|
||||
|
||||
// Create or get a ref for each document wrapper div (not the td)
|
||||
const getMenuRef = (docId: string) => {
|
||||
@@ -286,9 +286,9 @@ export default function Documents({
|
||||
/>
|
||||
) : (
|
||||
<div className="mt-8 flex w-full max-w-full flex-col overflow-hidden">
|
||||
<div className="relative flex grow flex-col">
|
||||
<div className="relative flex flex-grow flex-col">
|
||||
<div className="mb-6">
|
||||
<h2 className="text-sonic-silver text-base font-medium">
|
||||
<h2 className="text-base font-medium text-sonic-silver">
|
||||
{t('settings.documents.title')}
|
||||
</h2>
|
||||
</div>
|
||||
@@ -312,7 +312,7 @@ export default function Documents({
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="bg-purple-30 hover:bg-violets-are-blue flex h-[32px] min-w-[108px] items-center justify-center rounded-full px-4 text-sm whitespace-normal text-white"
|
||||
className="flex h-[32px] min-w-[108px] items-center justify-center whitespace-normal rounded-full bg-purple-30 px-4 text-sm text-white hover:bg-violets-are-blue"
|
||||
title={t('settings.documents.addNew')}
|
||||
onClick={() => {
|
||||
setIsOnboarding(false);
|
||||
@@ -323,15 +323,15 @@ export default function Documents({
|
||||
</button>
|
||||
</div>
|
||||
<div className="relative w-full">
|
||||
<div className="dark:border-silver/40 overflow-hidden rounded-md border border-gray-300">
|
||||
<div className="overflow-hidden rounded-md border border-gray-300 dark:border-silver/40">
|
||||
<div className="table-scroll overflow-x-auto">
|
||||
<table className="w-full table-auto">
|
||||
<thead>
|
||||
<tr className="dark:border-silver/40 border-b border-gray-300">
|
||||
<th className="text-sonic-silver w-[45%] px-4 py-3 text-left text-xs font-medium">
|
||||
<tr className="border-b border-gray-300 dark:border-silver/40">
|
||||
<th className="w-[45%] px-4 py-3 text-left text-xs font-medium text-sonic-silver">
|
||||
{t('settings.documents.name')}
|
||||
</th>
|
||||
<th className="text-sonic-silver w-[30%] px-4 py-3 text-left text-xs font-medium">
|
||||
<th className="w-[30%] px-4 py-3 text-left text-xs font-medium text-sonic-silver">
|
||||
<div className="flex items-center justify-start">
|
||||
{t('settings.documents.date')}
|
||||
<img
|
||||
@@ -342,7 +342,7 @@ export default function Documents({
|
||||
/>
|
||||
</div>
|
||||
</th>
|
||||
<th className="text-sonic-silver w-[15%] px-4 py-3 text-left text-xs font-medium">
|
||||
<th className="w-[15%] px-4 py-3 text-left text-xs font-medium text-sonic-silver">
|
||||
<div className="flex items-center justify-start">
|
||||
<span className="hidden sm:inline">
|
||||
{t('settings.documents.tokenUsage')}
|
||||
@@ -363,7 +363,7 @@ export default function Documents({
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="dark:divide-silver/40 divide-y divide-gray-300">
|
||||
<tbody className="divide-y divide-gray-300 dark:divide-silver/40">
|
||||
{loading ? (
|
||||
<SkeletonLoader component="table" />
|
||||
) : !currentDocuments?.length ? (
|
||||
@@ -382,15 +382,15 @@ export default function Documents({
|
||||
return (
|
||||
<tr key={docId} className="group transition-colors">
|
||||
<td
|
||||
className="max-w-0 min-w-48 truncate px-4 py-4 text-sm font-semibold text-gray-700 group-hover:bg-gray-50 dark:text-[#E0E0E0] dark:group-hover:bg-gray-800/50"
|
||||
className="min-w-48 max-w-0 truncate px-4 py-4 text-sm font-semibold text-gray-700 group-hover:bg-gray-50 dark:text-[#E0E0E0] dark:group-hover:bg-gray-800/50"
|
||||
title={document.name}
|
||||
>
|
||||
{document.name}
|
||||
</td>
|
||||
<td className="px-4 py-4 text-sm whitespace-nowrap text-gray-700 group-hover:bg-gray-50 dark:text-[#E0E0E0] dark:group-hover:bg-gray-800/50">
|
||||
<td className="whitespace-nowrap px-4 py-4 text-sm text-gray-700 group-hover:bg-gray-50 dark:text-[#E0E0E0] dark:group-hover:bg-gray-800/50">
|
||||
{document.date ? formatDate(document.date) : ''}
|
||||
</td>
|
||||
<td className="px-4 py-4 text-sm whitespace-nowrap text-gray-700 group-hover:bg-gray-50 dark:text-[#E0E0E0] dark:group-hover:bg-gray-800/50">
|
||||
<td className="whitespace-nowrap px-4 py-4 text-sm text-gray-700 group-hover:bg-gray-50 dark:text-[#E0E0E0] dark:group-hover:bg-gray-800/50">
|
||||
{document.tokens
|
||||
? formatTokens(+document.tokens)
|
||||
: ''}
|
||||
@@ -432,7 +432,7 @@ export default function Documents({
|
||||
)}
|
||||
<button
|
||||
onClick={(e) => handleMenuClick(e, docId)}
|
||||
className="inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-full transition-colors hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
className="inline-flex h-8 w-8 flex-shrink-0 items-center justify-center rounded-full transition-colors hover:bg-gray-100 dark:hover:bg-gray-700"
|
||||
aria-label="Open menu"
|
||||
data-testid={`menu-button-${docId}`}
|
||||
>
|
||||
@@ -634,7 +634,7 @@ function DocumentChunks({
|
||||
}, [page, perPage]);
|
||||
return (
|
||||
<div className="mt-8 flex flex-col">
|
||||
<div className="text-eerie-black dark:text-bright-gray mb-3 flex items-center gap-3 text-sm">
|
||||
<div className="mb-3 flex items-center gap-3 text-sm text-eerie-black dark:text-bright-gray">
|
||||
<button
|
||||
className="rounded-full border p-3 text-sm text-gray-400 dark:border-0 dark:bg-[#28292D] dark:text-gray-500 dark:hover:bg-[#2E2F34]"
|
||||
onClick={handleGoBack}
|
||||
@@ -644,7 +644,7 @@ function DocumentChunks({
|
||||
<p className="mt-px">{t('settings.documents.backToAll')}</p>
|
||||
</div>
|
||||
<div className="my-3 flex items-center justify-between gap-1">
|
||||
<div className="text-eerie-black dark:text-bright-gray flex w-full items-center gap-2 sm:w-auto">
|
||||
<div className="flex w-full items-center gap-2 text-eerie-black dark:text-bright-gray sm:w-auto">
|
||||
<p className="hidden text-2xl font-semibold sm:flex">{`${totalChunks} ${t('settings.documents.chunks')}`}</p>
|
||||
<label htmlFor="chunk-search-input" className="sr-only">
|
||||
{t('settings.documents.searchPlaceholder')}
|
||||
@@ -663,7 +663,7 @@ function DocumentChunks({
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="bg-purple-30 hover:bg-violets-are-blue flex h-[32px] min-w-[108px] items-center justify-center rounded-full px-4 text-sm whitespace-normal text-white"
|
||||
className="flex h-[32px] min-w-[108px] items-center justify-center whitespace-normal rounded-full bg-purple-30 px-4 text-sm text-white hover:bg-violets-are-blue"
|
||||
title={t('settings.documents.addNew')}
|
||||
onClick={() => setAddModal('ACTIVE')}
|
||||
>
|
||||
@@ -684,7 +684,7 @@ function DocumentChunks({
|
||||
.toLowerCase()
|
||||
.includes(searchTerm.toLowerCase());
|
||||
}).length === 0 ? (
|
||||
<div className="col-span-2 mt-24 text-center text-gray-500 lg:col-span-3 dark:text-gray-400">
|
||||
<div className="col-span-2 mt-24 text-center text-gray-500 dark:text-gray-400 lg:col-span-3">
|
||||
<img
|
||||
src={isDarkTheme ? NoFilesDarkIcon : NoFilesIcon}
|
||||
alt={t('settings.documents.noChunksAlt')}
|
||||
@@ -703,7 +703,7 @@ function DocumentChunks({
|
||||
.map((chunk, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="border-silver dark:border-silver/40 relative flex h-56 w-full flex-col justify-between rounded-2xl border p-6"
|
||||
className="relative flex h-56 w-full flex-col justify-between rounded-2xl border border-silver p-6 dark:border-silver/40"
|
||||
>
|
||||
<div className="w-full">
|
||||
<div className="flex w-full items-center justify-between">
|
||||
@@ -715,7 +715,7 @@ function DocumentChunks({
|
||||
chunk: chunk,
|
||||
});
|
||||
}}
|
||||
className="absolute top-3 right-3 h-4 w-4 cursor-pointer"
|
||||
className="absolute right-3 top-3 h-4 w-4 cursor-pointer"
|
||||
>
|
||||
<img
|
||||
alt={'edit'}
|
||||
@@ -725,10 +725,10 @@ function DocumentChunks({
|
||||
</button>
|
||||
</div>
|
||||
<div className="mt-[9px]">
|
||||
<p className="ellipsis-text text-eerie-black h-12 text-sm leading-relaxed font-semibold break-words dark:text-[#EEEEEE]">
|
||||
<p className="ellipsis-text h-12 break-words text-sm font-semibold leading-relaxed text-eerie-black dark:text-[#EEEEEE]">
|
||||
{chunk.metadata?.title ?? 'Untitled'}
|
||||
</p>
|
||||
<p className="mt-1 h-[110px] overflow-y-auto pr-1 text-[13px] leading-relaxed break-words text-gray-600 dark:text-gray-400">
|
||||
<p className="mt-1 h-[110px] overflow-y-auto break-words pr-1 text-[13px] leading-relaxed text-gray-600 dark:text-gray-400">
|
||||
{chunk.text}
|
||||
</p>
|
||||
</div>
|
||||
@@ -766,23 +766,19 @@ function DocumentChunks({
|
||||
setModalState={setAddModal}
|
||||
handleSubmit={handleAddChunk}
|
||||
/>
|
||||
{editModal.chunk && (
|
||||
<ChunkModal
|
||||
type="EDIT"
|
||||
modalState={editModal.state}
|
||||
setModalState={(state) =>
|
||||
setEditModal((prev) => ({ ...prev, state }))
|
||||
}
|
||||
handleSubmit={(title, text) => {
|
||||
handleUpdateChunk(title, text, editModal.chunk as ChunkType);
|
||||
}}
|
||||
originalText={editModal.chunk?.text ?? ''}
|
||||
originalTitle={editModal.chunk?.metadata?.title ?? ''}
|
||||
handleDelete={() => {
|
||||
handleDeleteChunk(editModal.chunk as ChunkType);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
<ChunkModal
|
||||
type="EDIT"
|
||||
modalState={editModal.state}
|
||||
setModalState={(state) => setEditModal((prev) => ({ ...prev, state }))}
|
||||
handleSubmit={(title, text) => {
|
||||
handleUpdateChunk(title, text, editModal.chunk as ChunkType);
|
||||
}}
|
||||
originalText={editModal.chunk?.text}
|
||||
originalTitle={editModal.chunk?.metadata?.title}
|
||||
handleDelete={() => {
|
||||
handleDeleteChunk(editModal.chunk as ChunkType);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -113,13 +113,13 @@ function LogsTable({ logs, setPage, loading, tableHeader }: LogsTableProps) {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="logs-table border-light-silver h-[55vh] w-full overflow-hidden rounded-xl border bg-white dark:border-transparent dark:bg-black">
|
||||
<div className="dark:bg-eerie-black-2 flex h-8 flex-col items-start justify-center bg-black/10">
|
||||
<p className="dark:text-gray-6000 px-3 text-xs">
|
||||
<div className="logs-table h-[55vh] w-full overflow-hidden rounded-xl border border-light-silver bg-white dark:border-transparent dark:bg-black">
|
||||
<div className="flex h-8 flex-col items-start justify-center bg-black/10 dark:bg-[#191919]">
|
||||
<p className="px-3 text-xs dark:text-gray-6000">
|
||||
{tableHeader ? tableHeader : t('settings.logs.tableHeader')}
|
||||
</p>
|
||||
</div>
|
||||
<div className="relative flex h-[51vh] grow flex-col items-start gap-2 overflow-y-auto overscroll-contain bg-transparent p-4">
|
||||
<div className="relative flex h-[51vh] flex-grow flex-col items-start gap-2 overflow-y-auto overscroll-contain bg-transparent p-4">
|
||||
{logs?.map((log, index) => {
|
||||
if (index === logs.length - 1) {
|
||||
return (
|
||||
@@ -164,7 +164,7 @@ function Log({
|
||||
const { id, action, timestamp, ...filteredLog } = log;
|
||||
|
||||
return (
|
||||
<div className="group dark:hover:bg-dark-charcoal w-full rounded-xl bg-transparent hover:bg-[#F9F9F9]">
|
||||
<div className="group w-full rounded-xl bg-transparent hover:bg-[#F9F9F9] hover:dark:bg-dark-charcoal">
|
||||
<div
|
||||
onClick={() => onToggle(log.id)}
|
||||
className={`flex cursor-pointer flex-row items-start gap-2 p-2 px-4 py-3 text-gray-900 ${
|
||||
@@ -177,7 +177,7 @@ function Log({
|
||||
className={`mt-[3px] h-3 w-3 transition duration-300 ${isOpen ? 'rotate-90' : ''}`}
|
||||
/>
|
||||
<span className="flex flex-row gap-2">
|
||||
<h2 className="dark:text-bright-gray text-xs text-black/60">{`${log.timestamp}`}</h2>
|
||||
<h2 className="text-xs text-black/60 dark:text-bright-gray">{`${log.timestamp}`}</h2>
|
||||
<h2 className="text-xs text-[#913400] dark:text-[#DF5200]">{`[${log.action}]`}</h2>
|
||||
<h2
|
||||
className={`max-w-72 text-xs ${logLevelColor[log.level]} break-words`}
|
||||
@@ -191,7 +191,7 @@ function Log({
|
||||
{isOpen && (
|
||||
<div className="rounded-b-xl bg-[#F1F1F1] px-4 py-3 dark:bg-[#1B1B1B]">
|
||||
<div className="scrollbar-thin overflow-y-auto">
|
||||
<pre className="px-2 font-mono text-xs leading-relaxed break-words whitespace-pre-wrap text-gray-700 dark:text-gray-400">
|
||||
<pre className="whitespace-pre-wrap break-words px-2 font-mono text-xs leading-relaxed text-gray-700 dark:text-gray-400">
|
||||
{JSON.stringify(filteredLog, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
@@ -177,7 +177,7 @@ export default function Prompts({
|
||||
rounded="3xl"
|
||||
border="border"
|
||||
showEdit
|
||||
showDelete={(prompt) => prompt.type !== 'public'}
|
||||
showDelete
|
||||
onEdit={({
|
||||
id,
|
||||
name,
|
||||
|
||||
@@ -162,7 +162,7 @@ export default function ToolConfig({
|
||||
return (
|
||||
<div className="scrollbar-thin mt-8 flex flex-col gap-4">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<div className="text-eerie-black dark:text-bright-gray flex items-center gap-3 text-sm">
|
||||
<div className="flex items-center gap-3 text-sm text-eerie-black dark:text-bright-gray">
|
||||
<button
|
||||
className="rounded-full border p-3 text-sm text-gray-400 dark:border-0 dark:bg-[#28292D] dark:text-gray-500 dark:hover:bg-[#2E2F34]"
|
||||
onClick={handleBackClick}
|
||||
@@ -172,7 +172,7 @@ export default function ToolConfig({
|
||||
<p className="mt-px">{t('settings.tools.backToAllTools')}</p>
|
||||
</div>
|
||||
<button
|
||||
className="bg-purple-30 hover:bg-violets-are-blue rounded-full px-3 py-2 text-xs text-nowrap text-white sm:px-4 sm:py-2"
|
||||
className="text-nowrap rounded-full bg-purple-30 px-3 py-2 text-xs text-white hover:bg-violets-are-blue sm:px-4 sm:py-2"
|
||||
onClick={handleSaveChanges}
|
||||
>
|
||||
{t('settings.tools.save')}
|
||||
@@ -180,7 +180,7 @@ export default function ToolConfig({
|
||||
</div>
|
||||
{/* Custom name section */}
|
||||
<div className="mt-1">
|
||||
<p className="text-eerie-black dark:text-bright-gray text-sm font-semibold">
|
||||
<p className="text-sm font-semibold text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.customName')}
|
||||
</p>
|
||||
<div className="relative mt-4 w-full max-w-96">
|
||||
@@ -195,7 +195,7 @@ export default function ToolConfig({
|
||||
</div>
|
||||
<div className="mt-1">
|
||||
{Object.keys(tool?.config).length !== 0 && tool.name !== 'api_tool' && (
|
||||
<p className="text-eerie-black dark:text-bright-gray text-sm font-semibold">
|
||||
<p className="text-sm font-semibold text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.authentication')}
|
||||
</p>
|
||||
)}
|
||||
@@ -217,7 +217,7 @@ export default function ToolConfig({
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="mx-0 my-2 h-[0.8px] w-full rounded-full bg-[#C4C4C4]/40"></div>
|
||||
<div className="flex w-full flex-row items-center justify-between gap-2">
|
||||
<p className="text-eerie-black dark:text-bright-gray text-base font-semibold">
|
||||
<p className="text-base font-semibold text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.actions')}
|
||||
</p>
|
||||
{tool.name === 'api_tool' &&
|
||||
@@ -225,7 +225,7 @@ export default function ToolConfig({
|
||||
Object.keys(tool.config.actions).length === 0) && (
|
||||
<button
|
||||
onClick={() => setActionModalState('ACTIVE')}
|
||||
className="border-violets-are-blue text-violets-are-blue hover:bg-violets-are-blue rounded-full border border-solid px-5 py-1 text-sm transition-colors hover:text-white"
|
||||
className="rounded-full border border-solid border-violets-are-blue px-5 py-1 text-sm text-violets-are-blue transition-colors hover:bg-violets-are-blue hover:text-white"
|
||||
>
|
||||
{t('settings.tools.addAction')}
|
||||
</button>
|
||||
@@ -255,10 +255,10 @@ export default function ToolConfig({
|
||||
tool.actions.map((action, actionIndex) => (
|
||||
<div
|
||||
key={actionIndex}
|
||||
className="border-silver dark:border-silver/40 w-full rounded-xl border"
|
||||
className="w-full rounded-xl border border-silver dark:border-silver/40"
|
||||
>
|
||||
<div className="border-silver dark:border-silver/40 flex h-10 flex-wrap items-center justify-between rounded-t-xl border-b bg-[#F9F9F9] px-5 dark:bg-[#28292D]">
|
||||
<p className="text-eerie-black dark:text-bright-gray font-semibold">
|
||||
<div className="flex h-10 flex-wrap items-center justify-between rounded-t-xl border-b border-silver bg-[#F9F9F9] px-5 dark:border-silver/40 dark:bg-[#28292D]">
|
||||
<p className="font-semibold text-eerie-black dark:text-bright-gray">
|
||||
{action.name}
|
||||
</p>
|
||||
<ToggleSwitch
|
||||
@@ -319,7 +319,7 @@ export default function ToolConfig({
|
||||
return (
|
||||
<tr
|
||||
key={index}
|
||||
className="font-normal text-nowrap"
|
||||
className="text-nowrap font-normal"
|
||||
>
|
||||
<td>{param[0]}</td>
|
||||
<td>{param[1].type}</td>
|
||||
@@ -334,7 +334,7 @@ export default function ToolConfig({
|
||||
checked={param[1].filled_by_llm}
|
||||
id={uniqueKey}
|
||||
type="checkbox"
|
||||
className="size-4 rounded-sm border-gray-300 bg-transparent"
|
||||
className="size-4 rounded border-gray-300 bg-transparent"
|
||||
onChange={() =>
|
||||
handleCheckboxChange(
|
||||
actionIndex,
|
||||
@@ -349,7 +349,7 @@ export default function ToolConfig({
|
||||
<input
|
||||
key={uniqueKey}
|
||||
value={param[1].description}
|
||||
className="border-silver dark:border-silver/40 rounded-lg border bg-transparent px-2 py-1 text-sm outline-hidden"
|
||||
className="rounded-lg border border-silver bg-transparent px-2 py-1 text-sm outline-none dark:border-silver/40"
|
||||
onChange={(e) => {
|
||||
setTool({
|
||||
...tool,
|
||||
@@ -385,7 +385,7 @@ export default function ToolConfig({
|
||||
value={param[1].value}
|
||||
key={uniqueKey}
|
||||
disabled={param[1].filled_by_llm}
|
||||
className={`border-silver dark:border-silver/40 rounded-lg border bg-transparent px-2 py-1 text-sm outline-hidden ${param[1].filled_by_llm ? 'opacity-50' : ''}`}
|
||||
className={`rounded-lg border border-silver bg-transparent px-2 py-1 text-sm outline-none dark:border-silver/40 ${param[1].filled_by_llm ? 'opacity-50' : ''}`}
|
||||
onChange={(e) => {
|
||||
setTool({
|
||||
...tool,
|
||||
@@ -563,10 +563,10 @@ function APIToolConfig({
|
||||
([actionName, action], actionIndex) => (
|
||||
<div
|
||||
key={actionIndex}
|
||||
className="border-silver dark:border-silver/40 w-full rounded-xl border"
|
||||
className="w-full rounded-xl border border-silver dark:border-silver/40"
|
||||
>
|
||||
<div className="border-silver dark:border-silver/40 flex h-10 flex-wrap items-center justify-between rounded-t-xl border-b bg-[#F9F9F9] px-5 dark:bg-[#28292D]">
|
||||
<p className="text-eerie-black dark:text-bright-gray font-semibold">
|
||||
<div className="flex h-10 flex-wrap items-center justify-between rounded-t-xl border-b border-silver bg-[#F9F9F9] px-5 dark:border-silver/40 dark:bg-[#28292D]">
|
||||
<p className="font-semibold text-eerie-black dark:text-bright-gray">
|
||||
{action.name}
|
||||
</p>
|
||||
<div className="flex items-center gap-2">
|
||||
@@ -618,7 +618,7 @@ function APIToolConfig({
|
||||
</div>
|
||||
<div className="mt-4 px-5 py-2">
|
||||
<div className="relative w-full">
|
||||
<span className="text-gray-4000 dark:bg-raisin-black dark:text-silver absolute -top-2 left-5 z-10 bg-white px-2 text-xs">
|
||||
<span className="absolute -top-2 left-5 z-10 bg-white px-2 text-xs text-gray-4000 dark:bg-raisin-black dark:text-silver">
|
||||
{t('settings.tools.method')}
|
||||
</span>
|
||||
<Dropdown
|
||||
@@ -867,14 +867,14 @@ function APIActionTable({
|
||||
<>
|
||||
{Object.entries(action[section].properties).map(
|
||||
([key, param], index) => (
|
||||
<tr key={index} className="font-normal text-nowrap">
|
||||
<tr key={index} className="text-nowrap font-normal">
|
||||
<td className="relative">
|
||||
{editingPropertyKey.section === section &&
|
||||
editingPropertyKey.oldKey === key ? (
|
||||
<div className="flex flex-row items-center justify-between gap-2">
|
||||
<input
|
||||
value={newPropertyKey}
|
||||
className="border-silver dark:border-silver/40 flex w-full min-w-[130.5px] items-start rounded-lg border bg-transparent px-2 py-1 text-sm outline-hidden"
|
||||
className="flex w-full min-w-[130.5px] items-start rounded-lg border border-silver bg-transparent px-2 py-1 text-sm outline-none dark:border-silver/40"
|
||||
onChange={(e) => setNewPropertyKey(e.target.value)}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
@@ -904,7 +904,7 @@ function APIActionTable({
|
||||
) : (
|
||||
<input
|
||||
value={key}
|
||||
className="border-silver dark:border-silver/40 flex w-full min-w-[175.5px] items-start rounded-lg border bg-transparent px-2 py-1 text-sm outline-hidden"
|
||||
className="flex w-full min-w-[175.5px] items-start rounded-lg border border-silver bg-transparent px-2 py-1 text-sm outline-none dark:border-silver/40"
|
||||
onFocus={() => handleRenamePropertyStart(section, key)}
|
||||
readOnly
|
||||
/>
|
||||
@@ -917,7 +917,7 @@ function APIActionTable({
|
||||
<input
|
||||
checked={param.filled_by_llm}
|
||||
type="checkbox"
|
||||
className="size-4 rounded-sm border-gray-300 bg-transparent"
|
||||
className="size-4 rounded border-gray-300 bg-transparent"
|
||||
onChange={(e) =>
|
||||
handlePropertyChange(
|
||||
section,
|
||||
@@ -933,7 +933,7 @@ function APIActionTable({
|
||||
<td className="w-10">
|
||||
<input
|
||||
value={param.description}
|
||||
className="border-silver dark:border-silver/40 rounded-lg border bg-transparent px-2 py-1 text-sm outline-hidden"
|
||||
className="rounded-lg border border-silver bg-transparent px-2 py-1 text-sm outline-none dark:border-silver/40"
|
||||
onChange={(e) =>
|
||||
handlePropertyChange(
|
||||
section,
|
||||
@@ -951,7 +951,7 @@ function APIActionTable({
|
||||
onChange={(e) =>
|
||||
handlePropertyChange(section, key, 'value', e.target.value)
|
||||
}
|
||||
className={`border-silver dark:border-silver/40 rounded-lg border bg-transparent px-2 py-1 text-sm outline-hidden ${param.filled_by_llm ? 'opacity-50' : ''}`}
|
||||
className={`rounded-lg border border-silver bg-transparent px-2 py-1 text-sm outline-none dark:border-silver/40 ${param.filled_by_llm ? 'opacity-50' : ''}`}
|
||||
></input>
|
||||
</td>
|
||||
<td
|
||||
@@ -961,7 +961,7 @@ function APIActionTable({
|
||||
maxWidth: '50px',
|
||||
padding: '0',
|
||||
}}
|
||||
className="border-silver dark:border-silver/40 border-b"
|
||||
className="border-b border-silver dark:border-silver/40"
|
||||
>
|
||||
<button
|
||||
onClick={() => handlePorpertyDelete(section, key)}
|
||||
@@ -985,13 +985,13 @@ function APIActionTable({
|
||||
}
|
||||
}}
|
||||
placeholder={t('settings.tools.propertyName')}
|
||||
className="border-silver dark:border-silver/40 flex w-full min-w-[130.5px] items-start rounded-lg border bg-transparent px-2 py-1 text-sm outline-hidden"
|
||||
className="flex w-full min-w-[130.5px] items-start rounded-lg border border-silver bg-transparent px-2 py-1 text-sm outline-none dark:border-silver/40"
|
||||
/>
|
||||
</td>
|
||||
<td colSpan={4} className="text-right">
|
||||
<button
|
||||
onClick={handleAddProperty}
|
||||
className="bg-purple-30 hover:bg-violets-are-blue mr-1 rounded-full px-5 py-[4px] text-sm text-white"
|
||||
className="mr-1 rounded-full bg-purple-30 px-5 py-[4px] text-sm text-white hover:bg-violets-are-blue"
|
||||
>
|
||||
{t('settings.tools.add')}
|
||||
</button>
|
||||
@@ -1016,7 +1016,7 @@ function APIActionTable({
|
||||
<td colSpan={5}>
|
||||
<button
|
||||
onClick={() => handleAddPropertyStart(section)}
|
||||
className="border-violets-are-blue text-violets-are-blue hover:bg-violets-are-blue flex items-start rounded-full border border-solid px-5 py-[4px] text-sm text-nowrap transition-colors hover:text-white"
|
||||
className="flex items-start text-nowrap rounded-full border border-solid border-violets-are-blue px-5 py-[4px] text-sm text-violets-are-blue transition-colors hover:bg-violets-are-blue hover:text-white"
|
||||
>
|
||||
{t('settings.tools.addNew')}
|
||||
</button>
|
||||
@@ -1037,25 +1037,25 @@ function APIActionTable({
|
||||
return (
|
||||
<div className="scrollbar-thin flex flex-col gap-6">
|
||||
<div>
|
||||
<h3 className="text-eerie-black dark:text-bright-gray mb-1 text-base font-normal">
|
||||
<h3 className="mb-1 text-base font-normal text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.headers')}
|
||||
</h3>
|
||||
<table className="table-default">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-eerie-black dark:text-bright-gray px-2 py-1 text-left text-sm font-normal">
|
||||
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.name')}
|
||||
</th>
|
||||
<th className="text-eerie-black dark:text-bright-gray px-2 py-1 text-left text-sm font-normal">
|
||||
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.type')}
|
||||
</th>
|
||||
<th className="text-eerie-black dark:text-bright-gray px-2 py-1 text-left text-sm font-normal">
|
||||
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.filledByLLM')}
|
||||
</th>
|
||||
<th className="text-eerie-black dark:text-bright-gray px-2 py-1 text-left text-sm font-normal">
|
||||
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.description')}
|
||||
</th>
|
||||
<th className="text-eerie-black dark:text-bright-gray px-2 py-1 text-left text-sm font-normal">
|
||||
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.value')}
|
||||
</th>
|
||||
<th
|
||||
@@ -1072,25 +1072,25 @@ function APIActionTable({
|
||||
</table>
|
||||
</div>
|
||||
<div>
|
||||
<h3 className="text-eerie-black dark:text-bright-gray mb-1 text-base font-normal">
|
||||
<h3 className="mb-1 text-base font-normal text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.queryParameters')}
|
||||
</h3>
|
||||
<table className="table-default">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-eerie-black dark:text-bright-gray px-2 py-1 text-left text-sm font-normal">
|
||||
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.name')}
|
||||
</th>
|
||||
<th className="text-eerie-black dark:text-bright-gray px-2 py-1 text-left text-sm font-normal">
|
||||
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.type')}
|
||||
</th>
|
||||
<th className="text-eerie-black dark:text-bright-gray px-2 py-1 text-left text-sm font-normal">
|
||||
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.filledByLLM')}
|
||||
</th>
|
||||
<th className="text-eerie-black dark:text-bright-gray px-2 py-1 text-left text-sm font-normal">
|
||||
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.description')}
|
||||
</th>
|
||||
<th className="text-eerie-black dark:text-bright-gray px-2 py-1 text-left text-sm font-normal">
|
||||
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.value')}
|
||||
</th>
|
||||
<th
|
||||
@@ -1107,25 +1107,25 @@ function APIActionTable({
|
||||
</table>
|
||||
</div>
|
||||
<div className="mb-6">
|
||||
<h3 className="text-eerie-black dark:text-bright-gray mb-1 text-base font-normal">
|
||||
<h3 className="mb-1 text-base font-normal text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.body')}
|
||||
</h3>
|
||||
<table className="table-default">
|
||||
<thead>
|
||||
<tr>
|
||||
<th className="text-eerie-black dark:text-bright-gray px-2 py-1 text-left text-sm font-normal">
|
||||
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.name')}
|
||||
</th>
|
||||
<th className="text-eerie-black dark:text-bright-gray px-2 py-1 text-left text-sm font-normal">
|
||||
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.type')}
|
||||
</th>
|
||||
<th className="text-eerie-black dark:text-bright-gray px-2 py-1 text-left text-sm font-normal">
|
||||
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.filledByLLM')}
|
||||
</th>
|
||||
<th className="text-eerie-black dark:text-bright-gray px-2 py-1 text-left text-sm font-normal">
|
||||
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.description')}
|
||||
</th>
|
||||
<th className="text-eerie-black dark:text-bright-gray px-2 py-1 text-left text-sm font-normal">
|
||||
<th className="px-2 py-1 text-left text-sm font-normal text-eerie-black dark:text-bright-gray">
|
||||
{t('settings.tools.value')}
|
||||
</th>
|
||||
<th
|
||||
|
||||
@@ -35,7 +35,7 @@ export default function Tools() {
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [activeMenuId, setActiveMenuId] = React.useState<string | null>(null);
|
||||
const menuRefs = React.useRef<{
|
||||
[key: string]: React.RefObject<HTMLDivElement | null>;
|
||||
[key: string]: React.RefObject<HTMLDivElement>;
|
||||
}>({});
|
||||
const [deleteModalState, setDeleteModalState] =
|
||||
React.useState<ActiveState>('INACTIVE');
|
||||
@@ -46,7 +46,7 @@ export default function Tools() {
|
||||
React.useEffect(() => {
|
||||
userTools.forEach((tool) => {
|
||||
if (!menuRefs.current[tool.id]) {
|
||||
menuRefs.current[tool.id] = React.createRef<HTMLDivElement>();
|
||||
menuRefs.current[tool.id] = React.createRef();
|
||||
}
|
||||
});
|
||||
}, [userTools]);
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
prefSlice,
|
||||
} from './preferences/preferenceSlice';
|
||||
import uploadReducer from './upload/uploadSlice';
|
||||
import agentPreviewReducer from './agents/agentPreviewSlice';
|
||||
|
||||
const key = localStorage.getItem('DocsGPTApiKey');
|
||||
const prompt = localStorage.getItem('DocsGPTPrompt');
|
||||
@@ -55,7 +54,6 @@ const store = configureStore({
|
||||
conversation: conversationSlice.reducer,
|
||||
sharedConversation: sharedConversationSlice.reducer,
|
||||
upload: uploadReducer,
|
||||
agentPreview: agentPreviewReducer,
|
||||
},
|
||||
middleware: (getDefaultMiddleware) =>
|
||||
getDefaultMiddleware().concat(prefListenerMiddleware.middleware),
|
||||
|
||||
@@ -70,7 +70,7 @@ function Upload({
|
||||
}`}
|
||||
>
|
||||
<div className="flex flex-col gap-4 overflow-hidden">
|
||||
<hr className="my-4 border border-[#C4C4C4]/40" />
|
||||
<hr className="my-4 border-[1px] border-[#C4C4C4]/40" />
|
||||
<div className="flex flex-col gap-4">
|
||||
{advancedFields.map((field: FormField) => renderField(field))}
|
||||
</div>
|
||||
@@ -150,10 +150,7 @@ function Upload({
|
||||
rounded="3xl"
|
||||
placeholder={field.label}
|
||||
border="border"
|
||||
buttonClassName="border-silver bg-white dark:border-dim-gray dark:bg-[#222327]"
|
||||
optionsClassName="border-silver bg-white dark:border-dim-gray dark:bg-[#383838]"
|
||||
placeholderClassName="text-gray-400 dark:text-silver"
|
||||
contentSize="text-sm"
|
||||
borderColor="gray-5000"
|
||||
/>
|
||||
);
|
||||
case 'boolean':
|
||||
@@ -197,7 +194,7 @@ function Upload({
|
||||
}>();
|
||||
|
||||
const { t } = useTranslation();
|
||||
const setTimeoutRef = useRef<number | null>(null);
|
||||
const setTimeoutRef = useRef<number | null>();
|
||||
|
||||
const urlOptions: { label: string; value: IngestorType }[] = [
|
||||
{ label: 'Crawler', value: 'crawler' },
|
||||
@@ -219,7 +216,7 @@ function Upload({
|
||||
<div className="relative h-32 w-32 rounded-full">
|
||||
<div className="absolute inset-0 rounded-full shadow-[0_0_10px_2px_rgba(0,0,0,0.3)_inset] dark:shadow-[0_0_10px_2px_rgba(0,0,0,0.3)_inset]"></div>
|
||||
<div
|
||||
className={`absolute inset-0 rounded-full ${progressPercent === 100 ? 'bg-linear-to-r from-white to-gray-400 shadow-xl shadow-lime-300/50 dark:bg-linear-to-br dark:from-gray-500 dark:to-gray-300 dark:shadow-lime-300/50' : 'shadow-[0_4px_0_#7D54D1] dark:shadow-[0_4px_0_#7D54D1]'}`}
|
||||
className={`absolute inset-0 rounded-full ${progressPercent === 100 ? 'bg-gradient-to-r from-white to-gray-400 shadow-xl shadow-lime-300/50 dark:bg-gradient-to-br dark:from-gray-500 dark:to-gray-300 dark:shadow-lime-300/50' : 'shadow-[0_4px_0_#7D54D1] dark:shadow-[0_4px_0_#7D54D1]'}`}
|
||||
style={{
|
||||
animation: `${progressPercent === 100 ? 'none' : 'rotate 2s linear infinite'}`,
|
||||
}}
|
||||
@@ -250,7 +247,7 @@ function Upload({
|
||||
isTraining?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="text-gray-2000 dark:text-bright-gray mt-5 flex flex-col items-center gap-2">
|
||||
<div className="mt-5 flex flex-col items-center gap-2 text-gray-2000 dark:text-bright-gray">
|
||||
<p className="text-gra text-xl tracking-[0.15px]">
|
||||
{isTraining &&
|
||||
(progress?.percentage === 100
|
||||
@@ -574,18 +571,18 @@ function Upload({
|
||||
} else {
|
||||
view = (
|
||||
<div className="flex w-full flex-col gap-4">
|
||||
<p className="text-jet dark:text-bright-gray text-center text-2xl font-semibold">
|
||||
<p className="text-center text-2xl font-semibold text-jet dark:text-bright-gray">
|
||||
{t('modals.uploadDoc.label')}
|
||||
</p>
|
||||
{!activeTab && (
|
||||
<div>
|
||||
<p className="dark text-gray-6000 dark:text-bright-gray text-center text-sm font-medium">
|
||||
<p className="dark text-center text-sm font-medium text-gray-6000 dark:text-bright-gray">
|
||||
{t('modals.uploadDoc.select')}
|
||||
</p>
|
||||
<div className="flex h-full w-full flex-col items-center justify-center gap-4 p-4 md:flex-row md:gap-4">
|
||||
<button
|
||||
onClick={() => setActiveTab('file')}
|
||||
className="hover:border-purple-30 hover:shadow-purple-30/30 flex h-40 w-40 flex-col items-center justify-center gap-4 rounded-3xl border border-[#D7D7D7] bg-transparent p-8 text-sm font-medium text-[#777777] opacity-85 hover:opacity-100 hover:shadow-lg md:h-52 md:w-52 dark:bg-transparent dark:text-[#c3c3c3]"
|
||||
className="flex h-40 w-40 flex-col items-center justify-center gap-4 rounded-3xl border border-[#D7D7D7] bg-transparent p-8 text-sm font-medium text-[#777777] opacity-85 hover:border-purple-30 hover:opacity-100 hover:shadow-lg hover:shadow-purple-30/30 dark:bg-transparent dark:text-[#c3c3c3] md:h-52 md:w-52"
|
||||
>
|
||||
<img
|
||||
src={FileUpload}
|
||||
@@ -595,7 +592,7 @@ function Upload({
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('remote')}
|
||||
className="hover:border-purple-30 hover:shadow-purple-30/30 flex h-40 w-40 flex-col items-center justify-center gap-4 rounded-3xl border border-[#D7D7D7] bg-transparent p-8 text-sm font-medium text-[#777777] opacity-85 hover:opacity-100 hover:shadow-lg md:h-52 md:w-52 dark:bg-transparent dark:text-[#c3c3c3]"
|
||||
className="flex h-40 w-40 flex-col items-center justify-center gap-4 rounded-3xl border border-[#D7D7D7] bg-transparent p-8 text-sm font-medium text-[#777777] opacity-85 hover:border-purple-30 hover:opacity-100 hover:shadow-lg hover:shadow-purple-30/30 dark:bg-transparent dark:text-[#c3c3c3] md:h-52 md:w-52"
|
||||
>
|
||||
<img
|
||||
src={WebsiteCollect}
|
||||
@@ -620,30 +617,30 @@ function Upload({
|
||||
required={true}
|
||||
/>
|
||||
<div className="my-2" {...getRootProps()}>
|
||||
<span className="text-purple-30 dark:text-silver rounded-3xl border border-[#7F7F82] bg-transparent px-4 py-2 font-medium hover:cursor-pointer">
|
||||
<span className="rounded-3xl border border-[#7F7F82] bg-transparent px-4 py-2 font-medium text-purple-30 hover:cursor-pointer dark:text-silver">
|
||||
<input type="button" {...getInputProps()} />
|
||||
{t('modals.uploadDoc.choose')}
|
||||
</span>
|
||||
</div>
|
||||
<p className="text-gray-4000 mb-0 text-xs italic">
|
||||
<p className="mb-0 text-xs italic text-gray-4000">
|
||||
{t('modals.uploadDoc.info')}
|
||||
</p>
|
||||
<div className="mt-0 max-w-full">
|
||||
<p className="text-eerie-black dark:text-light-gray mb-[14px] text-[14px] font-medium">
|
||||
<p className="mb-[14px] text-[14px] font-medium text-eerie-black dark:text-light-gray">
|
||||
{t('modals.uploadDoc.uploadedFiles')}
|
||||
</p>
|
||||
<div className="max-w-full overflow-hidden">
|
||||
{files.map((file) => (
|
||||
<p
|
||||
key={file.name}
|
||||
className="text-gray-6000 truncate overflow-hidden text-ellipsis"
|
||||
className="overflow-hidden truncate text-ellipsis text-gray-6000"
|
||||
title={file.name}
|
||||
>
|
||||
{file.name}
|
||||
</p>
|
||||
))}
|
||||
{files.length === 0 && (
|
||||
<p className="text-gray-6000 dark:text-light-gray text-[14px]">
|
||||
<p className="text-[14px] text-gray-6000 dark:text-light-gray">
|
||||
{t('none')}
|
||||
</p>
|
||||
)}
|
||||
@@ -654,6 +651,7 @@ function Upload({
|
||||
{activeTab === 'remote' && (
|
||||
<>
|
||||
<Dropdown
|
||||
border="border"
|
||||
options={urlOptions}
|
||||
selectedValue={
|
||||
urlOptions.find((opt) => opt.value === ingestor.type) || null
|
||||
@@ -662,10 +660,8 @@ function Upload({
|
||||
handleIngestorTypeChange(selected.value as IngestorType)
|
||||
}
|
||||
size="w-full"
|
||||
darkBorderColor="dim-gray"
|
||||
rounded="3xl"
|
||||
border="border"
|
||||
placeholder="Select ingestor type"
|
||||
placeholderClassName="text-gray-400 dark:text-silver"
|
||||
/>
|
||||
{/* Dynamically render form fields based on schema */}
|
||||
|
||||
@@ -685,7 +681,7 @@ function Upload({
|
||||
) && (
|
||||
<button
|
||||
onClick={() => setShowAdvancedOptions(!showAdvancedOptions)}
|
||||
className="text-purple-30 bg-transparent py-2 pl-0 text-left text-sm font-normal hover:cursor-pointer"
|
||||
className="bg-transparent py-2 pl-0 text-left text-sm font-normal text-purple-30 hover:cursor-pointer"
|
||||
>
|
||||
{showAdvancedOptions
|
||||
? t('modals.uploadDoc.hideAdvanced')
|
||||
@@ -698,7 +694,7 @@ function Upload({
|
||||
{activeTab && (
|
||||
<button
|
||||
onClick={() => setActiveTab(null)}
|
||||
className="text-purple-30 dark:text-silver rounded-3xl bg-transparent px-4 py-2 text-[14px] font-medium hover:cursor-pointer"
|
||||
className="rounded-3xl bg-transparent px-4 py-2 text-[14px] font-medium text-purple-30 hover:cursor-pointer dark:text-silver"
|
||||
>
|
||||
{t('modals.uploadDoc.back')}
|
||||
</button>
|
||||
@@ -716,7 +712,7 @@ function Upload({
|
||||
className={`rounded-3xl px-4 py-2 text-[14px] font-medium ${
|
||||
isUploadDisabled()
|
||||
? 'cursor-not-allowed bg-gray-300 text-gray-500'
|
||||
: 'bg-purple-30 hover:bg-violets-are-blue cursor-pointer text-white'
|
||||
: 'cursor-pointer bg-purple-30 text-white hover:bg-violets-are-blue'
|
||||
}`}
|
||||
>
|
||||
{t('modals.uploadDoc.train')}
|
||||
|
||||
90
frontend/tailwind.config.cjs
Normal file
90
frontend/tailwind.config.cjs
Normal file
@@ -0,0 +1,90 @@
|
||||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
|
||||
darkMode: 'class',
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
'roboto': ['Roboto', 'sans-serif'],
|
||||
},
|
||||
spacing: {
|
||||
112: '28rem',
|
||||
128: '32rem',
|
||||
},
|
||||
colors: {
|
||||
'eerie-black': '#212121',
|
||||
'black-1000': '#343541',
|
||||
'jet': '#343541',
|
||||
'gray-alpha': 'rgba(0,0,0, .64)',
|
||||
'gray-1000': '#F6F6F6',
|
||||
'gray-2000': 'rgba(0, 0, 0, 0.5)',
|
||||
'gray-3000': 'rgba(243, 243, 243, 1)',
|
||||
'gray-4000': '#949494',
|
||||
'gray-5000': '#BBBBBB',
|
||||
'gray-6000': '#757575',
|
||||
'red-1000': 'rgb(254, 202, 202)',
|
||||
'red-2000': '#F44336',
|
||||
'red-3000': '#621B16',
|
||||
'blue-1000': '#7D54D1',
|
||||
'blue-2000': '#002B49',
|
||||
'blue-3000': '#4B02E2',
|
||||
'purple-30': '#7D54D1',
|
||||
'purple-3000': 'rgb(230,222,247)',
|
||||
'blue-4000': 'rgba(0, 125, 255, 0.36)',
|
||||
'blue-5000': 'rgba(0, 125, 255)',
|
||||
'green-2000': '#0FFF50',
|
||||
'light-gray': '#edeef0',
|
||||
'white-3000': '#ffffff',
|
||||
'just-black': '#00000',
|
||||
'purple-taupe': '#464152',
|
||||
'dove-gray': '#6c6c6c',
|
||||
'silver': '#c4c4c4',
|
||||
'rainy-gray': '#a4a4a4',
|
||||
'raisin-black': '#222327',
|
||||
'chinese-black': '#161616',
|
||||
'chinese-silver': '#CDCDCD',
|
||||
'dark-charcoal': '#2F3036',
|
||||
'bright-gray': '#ECECF1',
|
||||
'outer-space': '#444654',
|
||||
'gun-metal': '#2E303E',
|
||||
'sonic-silver': '#747474',
|
||||
'soap': '#D8CCF1',
|
||||
'independence': '#54546D',
|
||||
'philippine-yellow': '#FFC700',
|
||||
'bright-gray': '#EBEBEB',
|
||||
'chinese-white': '#e0e0e0',
|
||||
'dark-gray': '#aaaaaa',
|
||||
'dim-gray': '#6A6A6A',
|
||||
'cultured': '#f4f4f4',
|
||||
'charleston-green': '#2b2c31',
|
||||
'charleston-green-2' : '#26272e',
|
||||
'charleston-green-3':'#26272A',
|
||||
'grey': '#7e7e7e',
|
||||
'lotion': '#FBFBFB',
|
||||
'platinum': '#e6e6e6',
|
||||
'eerie-black-2': '#191919',
|
||||
'light-silver': '#D9D9D9',
|
||||
'carbon': '#2E2E2E',
|
||||
'onyx':'#35363B',
|
||||
'royal-purple': '#6C4AB0',
|
||||
'chinese-black-2': '#0F1419',
|
||||
'gainsboro': '#D9DCDE',
|
||||
'onyx-2': '#35383C',
|
||||
'philippine-grey': '#929292',
|
||||
'charcoal-grey':'#53545D',
|
||||
'rosso-corsa': '#D30000',
|
||||
'north-texas-green': '#0C9D35',
|
||||
'medium-purple': '#8d66dd',
|
||||
'slate-blue': '#6f5fca',
|
||||
'old-silver': '#848484',
|
||||
'arsenic': '#4D4E58',
|
||||
'light-gainsboro': '#d7D7D7',
|
||||
'raisin-black-light': '#18181B',
|
||||
'gunmetal': '#32333B',
|
||||
'sonic-silver-light': '#7f7f82',
|
||||
'violets-are-blue':'#976AF3'
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
};
|
||||
Reference in New Issue
Block a user