Compare commits

..

1 Commits

Author SHA1 Message Date
dependabot[bot]
e211eb71bd build(deps): bump urllib3 from 2.3.0 to 2.5.0 in /application
---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-18 20:35:31 +00:00
73 changed files with 2248 additions and 2668 deletions

2
.gitattributes vendored
View File

@@ -1,2 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto

View File

@@ -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!

View File

@@ -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
)

View File

@@ -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

View File

@@ -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"
},
}
}

View File

@@ -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 {}

View File

@@ -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,
)
)

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View 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,
}

View 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,
}

View File

@@ -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,
}

View File

@@ -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.

View File

@@ -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)

View File

@@ -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"
}
}
}

View File

@@ -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>

View File

@@ -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>

File diff suppressed because it is too large Load Diff

View File

@@ -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"

View File

@@ -1,5 +1,6 @@
module.exports = {
plugins: {
'@tailwindcss/postcss': {},
tailwindcss: {},
autoprefixer: {},
},
}

View File

@@ -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

Binary file not shown.

View File

@@ -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">

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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]);

View File

@@ -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>

View File

@@ -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>

View File

@@ -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`}

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>;
}

View File

@@ -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>

View File

@@ -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,
);

View File

@@ -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}
</>
);
}

View File

@@ -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}

View File

@@ -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({

View File

@@ -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}

View File

@@ -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 && (

View File

@@ -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>

View File

@@ -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 ? (

View File

@@ -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>
)}

View File

@@ -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"

View File

@@ -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} />

View File

@@ -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>

View File

@@ -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);
}}

View File

@@ -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>

View File

@@ -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

View File

@@ -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>

View File

@@ -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>
);

View File

@@ -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

View File

@@ -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>

View File

@@ -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;
},

View File

@@ -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() {

View File

@@ -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);
}
}
}

View File

@@ -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>

View File

@@ -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')}
/>
)}

View File

@@ -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>

View File

@@ -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" />

View File

@@ -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'
: ''

View File

@@ -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}

View File

@@ -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>
);
}

View File

@@ -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>

View File

@@ -177,7 +177,7 @@ export default function Prompts({
rounded="3xl"
border="border"
showEdit
showDelete={(prompt) => prompt.type !== 'public'}
showDelete
onEdit={({
id,
name,

View File

@@ -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

View File

@@ -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]);

View File

@@ -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),

View File

@@ -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')}

View 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: [],
};