mirror of
https://github.com/arc53/DocsGPT.git
synced 2026-02-17 03:30:54 +00:00
Merge branch 'main' into fix/minor-bugs
This commit is contained in:
@@ -4,7 +4,7 @@ beautifulsoup4==4.12.3
|
||||
celery==5.4.0
|
||||
dataclasses-json==0.6.7
|
||||
docx2txt==0.8
|
||||
duckduckgo-search==6.3.0
|
||||
duckduckgo-search==7.4.2
|
||||
ebooklib==0.18
|
||||
elastic-transport==8.17.0
|
||||
elasticsearch==8.17.0
|
||||
|
||||
@@ -143,6 +143,7 @@ The DocsGPT Widget offers a range of customizable properties that allow you to t
|
||||
| **`buttonIcon`** | `string` | `"https://your-icon"` | URL for the icon image used in the widget's launch button. |
|
||||
| **`buttonBg`** | `string` | `"#222327"` | Background color of the widget's launch button. |
|
||||
| **`size`** | `"small" \| "medium"` | `"medium"` | Size of the widget. Options: `"small"` or `"medium"`. Defaults to `"medium"`. |
|
||||
| **`showSources`** | `boolean` | `false` | Enables displaying source URLs for data fetched within the widget. When set to `true`, the widget will show the original sources of the fetched data. |
|
||||
|
||||
---
|
||||
|
||||
|
||||
157
docs/pages/Guides/Architecture.mdx
Normal file
157
docs/pages/Guides/Architecture.mdx
Normal file
@@ -0,0 +1,157 @@
|
||||
---
|
||||
title: Architecture
|
||||
description: High-level architecture of DocsGPT
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
DocsGPT is designed as a modular and scalable application for knowledge based GenAI system. This document outlines the high-level architecture of DocsGPT, highlighting its key components.
|
||||
|
||||
## High-Level Architecture
|
||||
|
||||
This diagram provides a bird's-eye view of the DocsGPT architecture, illustrating the main components and their interactions.
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
User["User"] --> Frontend["Frontend (React/Vite)"]
|
||||
Frontend --> Backend["Backend API (Flask)"]
|
||||
Backend --> LLM["LLM Integration Layer"] & VectorStore["Vector Stores"] & TaskQueue["Task Queue (Celery)"] & Databases["Databases (MongoDB, Redis)"]
|
||||
LLM -- Cloud APIs / Local Engines --> InferenceEngine["Inference Engine"]
|
||||
VectorStore -- Document Embeddings --> Indexes[("Indexes")]
|
||||
TaskQueue -- Asynchronous Tasks --> DocumentIngestion["Document Ingestion"]
|
||||
|
||||
style Frontend fill:#AA00FF,color:#FFFFFF
|
||||
style Backend fill:#AA00FF,color:#FFFFFF
|
||||
style LLM fill:#AA00FF,color:#FFFFFF
|
||||
style TaskQueue fill:#AA00FF,color:#FFFFFF,stroke:#AA00FF
|
||||
style DocumentIngestion fill:#AA00FF,color:#FFFFFF,stroke:none
|
||||
```
|
||||
|
||||
## Component Descriptions
|
||||
|
||||
### 1. Frontend (React/Vite)
|
||||
|
||||
* **Technology:** Built using React and Vite.
|
||||
* **Responsibility:** This is the user interface of DocsGPT, providing users with an UI to ask questions and receive answers, configure prompts, tools and other settings. It handles user input, displays conversation history, shows sources, and manages settings.
|
||||
* **Key Features:**
|
||||
* Clean and responsive UI.
|
||||
* Simple static client-side rendering.
|
||||
* Manages conversation state and settings.
|
||||
* Communicates with the Backend API for data retrieval and processing.
|
||||
|
||||
### 2. Backend API (Flask)
|
||||
|
||||
* **Technology:** Implemented using Flask (Python).
|
||||
* **Responsibility:** The Backend API serves as the core logic and orchestration layer of DocsGPT. It receives requests from the Frontend, Extensions or API clients, processes them, and coordinates interactions between different components.
|
||||
* **Key Features:**
|
||||
* API endpoints for handling user queries, document uploads, and settings configurations.
|
||||
* Manages the overall application flow and logic.
|
||||
* Integrates with the LLM Integration Layer, Vector Stores, Task Queue, Tools, Agents and Databases.
|
||||
* Provides Swagger documentation for API endpoints.
|
||||
|
||||
### 3. LLM Integration Layer (Part of backend)
|
||||
|
||||
* **Technology:** Supports multiple LLM APIs and local engines.
|
||||
* **Responsibility:** This layer provides an abstraction for interacting with Large Language Models (LLMs).
|
||||
* **Key Features:**
|
||||
* Supports LLMs from OpenAI, Google, Anthropic, Groq, HuggingFace Inference API, Azure OpenAI, also compatable with local models like Ollama, LLaMa.cpp, Text Generation Inference (TGI), SGLang, vLLM, Aphrodite, FriendliAI, and LMDeploy.
|
||||
* Manages API key handling and request formatting and Tool fromatting.
|
||||
* Offers caching mechanisms to improve response times and reduce API usage.
|
||||
* Handles streaming responses for a more interactive user experience.
|
||||
|
||||
### 4. Vector Stores (Part of backend)
|
||||
|
||||
* **Technology:** Supports multiple vector databases.
|
||||
* **Responsibility:** Vector Stores are used to store and retrieve vector embeddings of document chunks. This enables semantic search and retrieval of relevant document snippets in response to user queries.
|
||||
* **Key Features:**
|
||||
* Supports vector databases including FAISS, Elasticsearch, Qdrant, Milvus, and LanceDB.
|
||||
* Provides storage and indexing of high-dimensional vector embeddings.
|
||||
* Enables editing and updating of vector indexes including specific chunks.
|
||||
|
||||
### 5. Parser Integration Layer (Part of backend)
|
||||
|
||||
* **Technology:** Supports multiple formats for file processing and remote source uploading.
|
||||
* **Responsibility:** Parser Integration Layer handles uploading, parsing, chunking, embedding, and indexing documents.
|
||||
* **Key Features:**
|
||||
* Supports various document formats (PDF, DOCX, TXT, etc.) and remote sources (web URLs, sitemaps).
|
||||
* Handles document parsing, text chunking, and embedding generation.
|
||||
* Utilizes Celery for asynchronous processing, ensuring efficient handling of large documents.
|
||||
|
||||
### 6. Task Queue (Celery)
|
||||
|
||||
* **Technology:** Celery with Redis as broker and backend.
|
||||
* **Responsibility:** Celery handles asynchronous task processing, for long-running operations such as document ingestion and indexing. This ensures that the main application remains responsive and efficient.
|
||||
* **Key Features:**
|
||||
* Manages background tasks for document processing and indexing.
|
||||
* Improves application responsiveness by offloading heavy tasks.
|
||||
* Enhances scalability and reliability through distributed task processing.
|
||||
|
||||
### 7. Databases (MongoDB, Redis)
|
||||
|
||||
* **Technology:** MongoDB and Redis.
|
||||
* **Responsibility:** Databases are used for persistent data storage and caching. MongoDB stores structured data such as conversations, documents, user settings, and API keys. Redis is used as a cache, as well as a message broker for Celery.
|
||||
|
||||
## Request Flow Diagram
|
||||
|
||||
This diagram illustrates the sequence of steps involved when a user submits a question to DocsGPT.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant User
|
||||
participant Frontend
|
||||
participant BackendAPI
|
||||
participant LLMIntegrationLayer
|
||||
participant VectorStores
|
||||
participant InferenceEngine
|
||||
|
||||
User->>Frontend: User asks a question
|
||||
Frontend->>BackendAPI: API Request (Question)
|
||||
BackendAPI->>VectorStores: Fetch relevant document chunks (Similarity Search)
|
||||
VectorStores-->>BackendAPI: Return document chunks
|
||||
BackendAPI->>LLMIntegrationLayer: Send question and document chunks
|
||||
LLMIntegrationLayer->>InferenceEngine: LLM API Request (Prompt + Context)
|
||||
InferenceEngine-->>LLMIntegrationLayer: LLM API Response (Answer)
|
||||
LLMIntegrationLayer-->>BackendAPI: Return Answer
|
||||
BackendAPI->>Frontend: API Response (Answer)
|
||||
Frontend->>User: Display Answer
|
||||
|
||||
Note over Frontend,BackendAPI: Data flow is simplified for clarity
|
||||
```
|
||||
|
||||
## Deployment Architecture
|
||||
|
||||
DocsGPT is designed to be deployed using Docker and Kubernetes, here is a qucik overview of a simple k8s deployment.
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
subgraph Kubernetes Cluster
|
||||
subgraph Nodes
|
||||
subgraph Node 1
|
||||
FrontendPod[Frontend Pod]
|
||||
BackendAPIPod[Backend API Pod]
|
||||
end
|
||||
subgraph Node 2
|
||||
CeleryWorkerPod[Celery Worker Pod]
|
||||
RedisPod[Redis Pod]
|
||||
end
|
||||
subgraph Node 3
|
||||
MongoDBPod[MongoDB Pod]
|
||||
VectorStorePod[Vector Store Pod]
|
||||
end
|
||||
end
|
||||
LoadBalancer[Load Balancer] --> docsgpt-frontend-service[docsgpt-frontend-service]
|
||||
LoadBalancer --> docsgpt-api-service[docsgpt-api-service]
|
||||
docsgpt-frontend-service --> FrontendPod
|
||||
docsgpt-api-service --> BackendAPIPod
|
||||
BackendAPIPod --> CeleryWorkerPod
|
||||
BackendAPIPod --> RedisPod
|
||||
BackendAPIPod --> MongoDBPod
|
||||
BackendAPIPod --> VectorStorePod
|
||||
CeleryWorkerPod --> RedisPod
|
||||
BackendAPIPod --> InferenceEngine[(Inference Engine)]
|
||||
VectorStorePod --> Indexes[(Indexes)]
|
||||
MongoDBPod --> Data[(Data)]
|
||||
RedisPod --> Cache[(Cache)]
|
||||
end
|
||||
User[User] --> LoadBalancer
|
||||
```
|
||||
@@ -16,5 +16,9 @@
|
||||
"title": "💭️ Avoiding hallucinations",
|
||||
"href": "/Guides/My-AI-answers-questions-using-external-knowledge",
|
||||
"display": "hidden"
|
||||
},
|
||||
"Architecture": {
|
||||
"title": "🏗️ Architecture",
|
||||
"href": "/Guides/Architecture"
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ export default function MyApp({ Component, pageProps }) {
|
||||
return (
|
||||
<>
|
||||
<Component {...pageProps} />
|
||||
<DocsGPTWidget apiKey="d61a020c-ac8f-4f23-bb98-458e4da3c240" theme="dark" size="medium" />
|
||||
<DocsGPTWidget showSources={true} apiKey="6dd66edf-d374-4904-93af-ab0c6d41ee56" theme="dark" size="medium" />
|
||||
</>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export default function Hero({
|
||||
<Fragment key={key}>
|
||||
<button
|
||||
onClick={() => handleQuestion({ question: demo.query })}
|
||||
className="w-full rounded-full border border-silver px-6 py-4 text-left hover:border-gray-4000 dark:hover:border-gray-3000 xl:min-w-[24vw] bg-white dark:bg-raisin-black focus:outline-none focus:ring-2 focus:ring-purple-taupe"
|
||||
className="w-full rounded-full border border-silver px-6 py-4 text-left hover:border-gray-4000 dark:hover:border-gray-3000 xl:min-w-[24vw] bg-white dark:bg-raisin-black focus:outline-none"
|
||||
>
|
||||
<p className="mb-1 font-semibold text-black-1000 dark:text-bright-gray">
|
||||
{demo.header}
|
||||
|
||||
@@ -119,7 +119,7 @@ function Dropdown({
|
||||
{options.map((option: any, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="hover:eerie-black flex cursor-pointer items-center justify-between hover:bg-gray-100 dark:hover:bg-purple-taupe"
|
||||
className="hover:eerie-black flex cursor-pointer items-center justify-between hover:bg-gray-100 dark:hover:bg-[#545561]"
|
||||
>
|
||||
<span
|
||||
onClick={() => {
|
||||
|
||||
@@ -47,7 +47,7 @@ const Input = ({
|
||||
</input>
|
||||
{label && (
|
||||
<div className="absolute -top-2 left-2">
|
||||
<span className="bg-white px-2 text-xs text-gray-4000 dark:bg-outer-space dark:text-silver flex items-center">
|
||||
<span className="bg-white px-2 text-xs text-gray-4000 dark:bg-[#26272E] dark:text-silver flex items-center">
|
||||
{label}
|
||||
{required && (
|
||||
<span className="text-[#D30000] dark:text-[#D42626] ml-0.5">
|
||||
|
||||
@@ -83,7 +83,7 @@ function SourceDropdown({
|
||||
return (
|
||||
<div
|
||||
key={index}
|
||||
className="flex cursor-pointer items-center justify-between hover:bg-gray-100 dark:text-bright-gray dark:hover:bg-purple-taupe"
|
||||
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);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Exit from '../assets/exit.svg';
|
||||
import WrapperModal from './WrapperModal';
|
||||
import Input from '../components/Input';
|
||||
import { ActiveState } from '../models/misc';
|
||||
|
||||
@@ -35,23 +35,53 @@ export default function AddActionModal({
|
||||
setActionName('');
|
||||
setModalState('INACTIVE');
|
||||
};
|
||||
|
||||
if (modalState !== 'ACTIVE') return null;
|
||||
return (
|
||||
<div
|
||||
className={`${
|
||||
modalState === 'ACTIVE' ? 'visible' : 'hidden'
|
||||
} fixed top-0 left-0 z-30 h-screen w-screen bg-gray-alpha flex items-center justify-center`}
|
||||
<WrapperModal
|
||||
close={() => setModalState('INACTIVE')}
|
||||
className="sm:w-[512px]"
|
||||
>
|
||||
<article className="flex w-11/12 sm:w-[512px] flex-col gap-4 rounded-2xl bg-white shadow-lg dark:bg-[#26272E]">
|
||||
<div className="relative">
|
||||
<div>
|
||||
<h2 className="font-semibold text-xl text-jet dark:text-bright-gray px-3">
|
||||
New Action
|
||||
</h2>
|
||||
<div className="mt-6 px-3">
|
||||
<Input
|
||||
type="text"
|
||||
value={actionName}
|
||||
onChange={(e) => setActionName(e.target.value)}
|
||||
borderVariant="thin"
|
||||
placeholder="Enter name"
|
||||
label="Action Name"
|
||||
/>
|
||||
<p className="mt-1 text-gray-500 text-xs">
|
||||
Use only letters, numbers, underscores, and hyphens (e.g.,
|
||||
`get_user_data`, `send-report`).
|
||||
</p>
|
||||
{functionNameError && (
|
||||
<p className="mt-1 text-red-500 text-xs">
|
||||
Invalid function name format. Use only letters, numbers,
|
||||
underscores, and hyphens.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-8 flex flex-row-reverse gap-1 px-3">
|
||||
<button
|
||||
onClick={handleAddAction}
|
||||
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
<button
|
||||
className="absolute top-3 right-4 m-2 w-3"
|
||||
onClick={() => {
|
||||
setFunctionNameError(false);
|
||||
setModalState('INACTIVE');
|
||||
setActionName('');
|
||||
}}
|
||||
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"
|
||||
>
|
||||
<img className="filter dark:invert" src={Exit} />
|
||||
{t('modals.configTool.closeButton')}
|
||||
</button>
|
||||
<div className="p-6">
|
||||
<h2 className="font-semibold text-xl text-jet dark:text-bright-gray px-3">
|
||||
@@ -102,7 +132,7 @@ export default function AddActionModal({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</WrapperModal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,12 @@ import React, { useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import userService from '../api/services/userService';
|
||||
import Exit from '../assets/exit.svg';
|
||||
import { useOutsideAlerter } from '../hooks';
|
||||
import { ActiveState } from '../models/misc';
|
||||
import ConfigToolModal from './ConfigToolModal';
|
||||
import { AvailableToolType } from './types';
|
||||
import Spinner from '../components/Spinner';
|
||||
import WrapperComponent from './WrapperModal';
|
||||
|
||||
export default function AddToolModal({
|
||||
message,
|
||||
@@ -91,28 +91,12 @@ export default function AddToolModal({
|
||||
}, [modalState]);
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={`${
|
||||
modalState === 'ACTIVE' ? 'visible' : 'hidden'
|
||||
} fixed top-0 left-0 z-30 h-screen w-screen bg-gray-alpha flex items-center justify-center`}
|
||||
>
|
||||
<article
|
||||
ref={modalRef}
|
||||
className="flex h-[85vh] w-[90vw] md:w-[75vw] flex-col gap-4 rounded-2xl bg-[#FBFBFB] shadow-lg dark:bg-[#26272E]"
|
||||
{modalState === 'ACTIVE' && (
|
||||
<WrapperComponent
|
||||
close={() => setModalState('INACTIVE')}
|
||||
className="h-[85vh] w-[90vw] md:w-[75vw]"
|
||||
>
|
||||
<div className="relative">
|
||||
<button
|
||||
className="absolute top-3 right-4 m-2 w-3"
|
||||
onClick={() => {
|
||||
setModalState('INACTIVE');
|
||||
}}
|
||||
>
|
||||
<img
|
||||
className="filter dark:invert"
|
||||
src={Exit}
|
||||
alt={t('cancel')}
|
||||
/>
|
||||
</button>
|
||||
<div className="flex flex-col gap-4 h-full">
|
||||
<div className="p-6">
|
||||
<h2 className="font-semibold text-xl text-jet dark:text-bright-gray px-3">
|
||||
{t('settings.tools.selectToolSetup')}
|
||||
@@ -165,8 +149,8 @@ export default function AddToolModal({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</WrapperComponent>
|
||||
)}
|
||||
<ConfigToolModal
|
||||
modalState={configModalState}
|
||||
setModalState={setConfigModalState}
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Exit from '../assets/exit.svg';
|
||||
import WrapperModal from './WrapperModal';
|
||||
import Input from '../components/Input';
|
||||
import { ActiveState } from '../models/misc';
|
||||
import { AvailableToolType } from './types';
|
||||
import userService from '../api/services/userService';
|
||||
|
||||
interface ConfigToolModalProps {
|
||||
modalState: ActiveState;
|
||||
setModalState: (state: ActiveState) => void;
|
||||
tool: AvailableToolType | null;
|
||||
getUserTools: () => void;
|
||||
}
|
||||
|
||||
export default function ConfigToolModal({
|
||||
modalState,
|
||||
setModalState,
|
||||
tool,
|
||||
getUserTools,
|
||||
}: {
|
||||
modalState: ActiveState;
|
||||
setModalState: (state: ActiveState) => void;
|
||||
tool: AvailableToolType | null;
|
||||
getUserTools: () => void;
|
||||
}) {
|
||||
}: ConfigToolModalProps) {
|
||||
const { t } = useTranslation();
|
||||
const [authKey, setAuthKey] = React.useState<string>('');
|
||||
|
||||
@@ -36,21 +38,44 @@ export default function ConfigToolModal({
|
||||
getUserTools();
|
||||
});
|
||||
};
|
||||
|
||||
// Only render when modal is active
|
||||
if (modalState !== 'ACTIVE') return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`${
|
||||
modalState === 'ACTIVE' ? 'visible' : 'hidden'
|
||||
} fixed top-0 left-0 z-30 h-screen w-screen bg-gray-alpha flex items-center justify-center`}
|
||||
>
|
||||
<article className="flex w-11/12 sm:w-[512px] flex-col gap-4 rounded-2xl bg-white shadow-lg dark:bg-[#26272E]">
|
||||
<div className="relative">
|
||||
<WrapperModal close={() => setModalState('INACTIVE')}>
|
||||
<div>
|
||||
<h2 className="font-semibold text-xl text-jet dark:text-bright-gray px-3">
|
||||
{t('modals.configTool.title')}
|
||||
</h2>
|
||||
<p className="mt-5 text-sm text-gray-600 dark:text-gray-400 px-3">
|
||||
{t('modals.configTool.type')}:{' '}
|
||||
<span className="font-semibold">{tool?.name}</span>
|
||||
</p>
|
||||
<div className="mt-6 px-3">
|
||||
<Input
|
||||
type="text"
|
||||
value={authKey}
|
||||
onChange={(e) => setAuthKey(e.target.value)}
|
||||
borderVariant="thin"
|
||||
placeholder={t('modals.configTool.apiKeyPlaceholder')}
|
||||
label={t('modals.configTool.apiKeyLabel')}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-8 flex flex-row-reverse gap-1 px-3">
|
||||
<button
|
||||
className="absolute top-3 right-4 m-2 w-3"
|
||||
onClick={() => {
|
||||
setModalState('INACTIVE');
|
||||
tool && handleAddTool(tool);
|
||||
}}
|
||||
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]"
|
||||
>
|
||||
<img className="filter dark:invert" src={Exit} />
|
||||
{t('modals.configTool.addButton')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setModalState('INACTIVE')}
|
||||
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.configTool.closeButton')}
|
||||
</button>
|
||||
<div className="p-6">
|
||||
<h2 className="font-semibold text-xl text-jet dark:text-bright-gray px-3">
|
||||
@@ -92,7 +117,7 @@ export default function ConfigToolModal({
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</WrapperModal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -73,21 +73,20 @@ export default function CreateAPIKeyModal({
|
||||
handleFetchPrompts();
|
||||
}, []);
|
||||
return (
|
||||
<WrapperModal close={close}>
|
||||
<WrapperModal close={close} className="p-4">
|
||||
<div className="mb-6">
|
||||
<span className="text-xl text-jet dark:text-bright-gray">
|
||||
{t('modals.createAPIKey.label')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative mt-5 mb-4">
|
||||
<span className="absolute left-2 -top-2 bg-white px-2 text-xs text-gray-4000 dark:bg-outer-space dark:text-silver">
|
||||
{t('modals.createAPIKey.apiKeyName')}
|
||||
</span>
|
||||
<Input
|
||||
type="text"
|
||||
className="rounded-md"
|
||||
value={APIKeyName}
|
||||
label={t('modals.createAPIKey.apiKeyName')}
|
||||
onChange={(e) => setAPIKeyName(e.target.value)}
|
||||
borderVariant="thin"
|
||||
></Input>
|
||||
</div>
|
||||
<div className="my-4">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import Exit from '../assets/exit.svg';
|
||||
import { SaveAPIKeyModalProps } from '../models/misc';
|
||||
import WrapperModal from './WrapperModal';
|
||||
|
||||
export default function SaveAPIKeyModal({
|
||||
apiKey,
|
||||
@@ -15,38 +15,37 @@ export default function SaveAPIKeyModal({
|
||||
navigator.clipboard.writeText(apiKey);
|
||||
setIsCopied(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed top-0 left-0 z-30 flex h-screen w-screen items-center justify-center bg-gray-alpha bg-opacity-50">
|
||||
<div className="relative w-11/12 rounded-3xl bg-white px-6 py-8 dark:bg-outer-space dark:text-bright-gray sm:w-[512px]">
|
||||
<button className="absolute top-3 right-4 m-2 w-3" onClick={close}>
|
||||
<img className="filter dark:invert" src={Exit} />
|
||||
</button>
|
||||
<h1 className="my-0 text-xl font-medium">
|
||||
{' '}
|
||||
{t('modals.saveKey.note')}
|
||||
</h1>
|
||||
<h3 className="text-sm font-normal text-outer-space">
|
||||
{t('modals.saveKey.disclaimer')}
|
||||
</h3>
|
||||
<div className="flex justify-between py-2">
|
||||
<div>
|
||||
<h2 className="text-base font-semibold">API Key</h2>
|
||||
<span className="text-sm font-normal leading-7 ">{apiKey}</span>
|
||||
</div>
|
||||
<button
|
||||
className="my-1 h-10 w-20 rounded-full border border-solid border-purple-30 p-2 text-sm text-purple-30 hover:bg-purple-30 hover:text-white"
|
||||
onClick={handleCopyKey}
|
||||
>
|
||||
{isCopied ? t('modals.saveKey.copied') : t('modals.saveKey.copy')}
|
||||
</button>
|
||||
<WrapperModal close={close}>
|
||||
<h1 className="my-0 text-xl font-medium text-jet dark:text-bright-gray">
|
||||
{t('modals.saveKey.note')}
|
||||
</h1>
|
||||
<h3 className="text-sm font-normal text-outer-space dark:text-silver">
|
||||
{t('modals.saveKey.disclaimer')}
|
||||
</h3>
|
||||
<div className="flex justify-between py-2">
|
||||
<div>
|
||||
<h2 className="text-base font-semibold text-jet dark:text-bright-gray">
|
||||
API Key
|
||||
</h2>
|
||||
<span className="text-sm font-normal leading-7 text-jet dark:text-bright-gray">
|
||||
{apiKey}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={close}
|
||||
className="rounded-full bg-philippine-yellow px-4 py-3 font-medium text-black hover:bg-[#E6B91A]"
|
||||
className="my-1 h-10 w-20 rounded-full border border-solid border-purple-30 p-2 text-sm text-purple-30 hover:bg-purple-30 hover:text-white"
|
||||
onClick={handleCopyKey}
|
||||
>
|
||||
{t('modals.saveKey.confirm')}
|
||||
{isCopied ? t('modals.saveKey.copied') : t('modals.saveKey.copy')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={close}
|
||||
className="rounded-full bg-philippine-yellow px-4 py-3 font-medium text-black hover:bg-[#E6B91A]"
|
||||
>
|
||||
{t('modals.saveKey.confirm')}
|
||||
</button>
|
||||
</WrapperModal>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
import Exit from '../assets/exit.svg';
|
||||
import { WrapperModalPropsType } from './types';
|
||||
|
||||
interface WrapperModalPropsType {
|
||||
children: React.ReactNode;
|
||||
close: () => void;
|
||||
isPerformingTask?: boolean;
|
||||
className?: string;
|
||||
contentClassName?: string;
|
||||
}
|
||||
|
||||
export default function WrapperModal({
|
||||
children,
|
||||
close,
|
||||
isPerformingTask,
|
||||
isPerformingTask = false,
|
||||
className = '', // Default width, but can be overridden
|
||||
contentClassName = '', // Default padding, but can be overridden
|
||||
}: WrapperModalPropsType) {
|
||||
const modalRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@@ -40,17 +49,17 @@ export default function WrapperModal({
|
||||
<div className="fixed top-0 left-0 z-30 flex h-screen w-screen items-center justify-center bg-gray-alpha bg-opacity-50">
|
||||
<div
|
||||
ref={modalRef}
|
||||
className="relative w-11/12 rounded-2xl bg-white dark:bg-outer-space sm:w-[512px] p-8"
|
||||
className={`relative w-11/12 sm:w-[512px] p-8 rounded-2xl bg-white dark:bg-[#26272E] ${className}`}
|
||||
>
|
||||
{!isPerformingTask && (
|
||||
<button
|
||||
className="absolute top-3 right-4 m-2 w-3 z-50"
|
||||
onClick={close}
|
||||
>
|
||||
<img className="filter dark:invert" src={Exit} />
|
||||
<img className="filter dark:invert" src={Exit} alt="Close" />
|
||||
</button>
|
||||
)}
|
||||
{children}
|
||||
<div className={`${contentClassName}`}>{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import * as React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
interface ModalProps {
|
||||
handleSubmit: () => void;
|
||||
isCancellable: boolean;
|
||||
handleCancel?: () => void;
|
||||
render: () => JSX.Element;
|
||||
modalState: string;
|
||||
isError: boolean;
|
||||
errorMessage?: string;
|
||||
textDelete?: boolean;
|
||||
}
|
||||
|
||||
const Modal = (props: ModalProps) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div
|
||||
className={`${
|
||||
props.modalState === 'ACTIVE' ? 'visible' : 'hidden'
|
||||
} absolute z-30 h-screen w-screen bg-gray-alpha`}
|
||||
>
|
||||
{props.render()}
|
||||
<div className=" mx-auto flex w-[90vw] max-w-lg flex-row-reverse rounded-b-lg bg-white pb-5 pr-5 shadow-lg dark:bg-outer-space">
|
||||
<div>
|
||||
<button
|
||||
onClick={() => props.handleSubmit()}
|
||||
className="ml-auto h-10 w-20 rounded-3xl bg-violet-800 text-white transition-all hover:bg-violet-700 dark:text-silver"
|
||||
>
|
||||
{props.textDelete ? 'Delete' : 'Save'}
|
||||
</button>
|
||||
{props.isCancellable && (
|
||||
<button
|
||||
onClick={() => props.handleCancel && props.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"
|
||||
>
|
||||
{t('cancel')}
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
{props.isError && (
|
||||
<p className="mx-auto mt-2 mr-auto text-sm text-red-500">
|
||||
{props.errorMessage}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Modal;
|
||||
@@ -1,80 +0,0 @@
|
||||
import { useRef, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { ActiveState } from '../models/misc';
|
||||
import { selectApiKey, setApiKey } from './preferenceSlice';
|
||||
import { useMediaQuery, useOutsideAlerter } from './../hooks';
|
||||
import Modal from '../modals';
|
||||
import Input from '../components/Input';
|
||||
|
||||
export default function APIKeyModal({
|
||||
modalState,
|
||||
setModalState,
|
||||
isCancellable = true,
|
||||
}: {
|
||||
modalState: ActiveState;
|
||||
setModalState: (val: ActiveState) => void;
|
||||
isCancellable?: boolean;
|
||||
}) {
|
||||
const dispatch = useDispatch();
|
||||
const apiKey = useSelector(selectApiKey);
|
||||
const [key, setKey] = useState(apiKey);
|
||||
const [isError, setIsError] = useState(false);
|
||||
const modalRef = useRef(null);
|
||||
const { isMobile } = useMediaQuery();
|
||||
|
||||
useOutsideAlerter(modalRef, () => {
|
||||
if (isMobile && modalState === 'ACTIVE') {
|
||||
setModalState('INACTIVE');
|
||||
}
|
||||
}, [modalState]);
|
||||
|
||||
function handleSubmit() {
|
||||
if (key.length <= 1) {
|
||||
setIsError(true);
|
||||
} else {
|
||||
dispatch(setApiKey(key));
|
||||
setModalState('INACTIVE');
|
||||
setIsError(false);
|
||||
}
|
||||
}
|
||||
|
||||
function handleCancel() {
|
||||
setKey(apiKey);
|
||||
setIsError(false);
|
||||
setModalState('INACTIVE');
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
handleCancel={handleCancel}
|
||||
isError={isError}
|
||||
modalState={modalState}
|
||||
isCancellable={isCancellable}
|
||||
handleSubmit={handleSubmit}
|
||||
render={() => {
|
||||
return (
|
||||
<article
|
||||
ref={modalRef}
|
||||
className="mx-auto mt-24 flex w-[90vw] max-w-lg flex-col gap-4 rounded-t-lg bg-white p-6 shadow-lg"
|
||||
>
|
||||
<p className="text-xl text-jet">OpenAI API Key</p>
|
||||
<p className="text-md leading-6 text-gray-500">
|
||||
Before you can start using DocsGPT we need you to provide an API
|
||||
key for llm. Currently, we support only OpenAI but soon many more.
|
||||
You can find it here.
|
||||
</p>
|
||||
<Input
|
||||
type="text"
|
||||
colorVariant="jet"
|
||||
className="h-10 border-b-2 focus:outline-none"
|
||||
value={key}
|
||||
maxLength={100}
|
||||
placeholder="API Key"
|
||||
onChange={(e) => setKey(e.target.value)}
|
||||
></Input>
|
||||
</article>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
import { ActiveState } from '../models/misc';
|
||||
import Exit from '../assets/exit.svg';
|
||||
import Input from '../components/Input';
|
||||
import React from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import WrapperModal from '../modals/WrapperModal';
|
||||
|
||||
function AddPrompt({
|
||||
setModalState,
|
||||
@@ -24,69 +24,52 @@ function AddPrompt({
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<button
|
||||
className="absolute top-3 right-4 m-2 w-3"
|
||||
onClick={() => {
|
||||
setModalState('INACTIVE');
|
||||
setNewPromptName('');
|
||||
setNewPromptContent('');
|
||||
}}
|
||||
aria-label="Close add prompt modal"
|
||||
>
|
||||
<img className="filter dark:invert" src={Exit} alt="Close modal" />
|
||||
</button>
|
||||
<div className="p-8">
|
||||
<p className="mb-1 text-xl text-jet dark:text-bright-gray">
|
||||
{t('modals.prompts.addPrompt')}
|
||||
</p>
|
||||
<p className="mb-7 text-xs text-[#747474] dark:text-[#7F7F82]">
|
||||
{t('modals.prompts.addDescription')}
|
||||
</p>
|
||||
<div>
|
||||
<label htmlFor="new-prompt-name" className="sr-only">
|
||||
Prompt Name
|
||||
</label>
|
||||
<Input
|
||||
placeholder={t('modals.prompts.promptName')}
|
||||
type="text"
|
||||
className="h-10 rounded-lg"
|
||||
value={newPromptName}
|
||||
onChange={(e) => setNewPromptName(e.target.value)}
|
||||
/>
|
||||
<div className="relative bottom-12 left-3 mt-[-3.00px]">
|
||||
<span className="bg-white px-1 text-xs text-silver dark:bg-outer-space dark:text-silver">
|
||||
{t('modals.prompts.promptName')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative top-[7px] left-3">
|
||||
<span className="bg-white px-1 text-xs text-silver dark:bg-outer-space dark:text-silver">
|
||||
{t('modals.prompts.promptText')}
|
||||
</span>
|
||||
</div>
|
||||
<label htmlFor="new-prompt-content" className="sr-only">
|
||||
Prompt Text
|
||||
</label>
|
||||
<textarea
|
||||
id="new-prompt-content"
|
||||
className="h-56 w-full rounded-lg border-2 border-silver px-3 py-2 outline-none dark:border-silver/40 dark:bg-transparent dark:text-white"
|
||||
value={newPromptContent}
|
||||
onChange={(e) => setNewPromptContent(e.target.value)}
|
||||
aria-label="Prompt Text"
|
||||
></textarea>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-row-reverse">
|
||||
<button
|
||||
onClick={handleAddPrompt}
|
||||
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:opacity-90"
|
||||
disabled={disableSave}
|
||||
title={
|
||||
disableSave && newPromptName ? t('modals.prompts.nameExists') : ''
|
||||
}
|
||||
>
|
||||
{t('modals.prompts.save')}
|
||||
</button>
|
||||
<div>
|
||||
<p className="mb-1 text-xl text-jet dark:text-bright-gray">
|
||||
{t('modals.prompts.addPrompt')}
|
||||
</p>
|
||||
<p className="mb-7 text-xs text-[#747474] dark:text-[#7F7F82]">
|
||||
{t('modals.prompts.addDescription')}
|
||||
</p>
|
||||
<div>
|
||||
<label htmlFor="new-prompt-name" className="sr-only">
|
||||
Prompt Name
|
||||
</label>
|
||||
<Input
|
||||
placeholder={t('modals.prompts.promptName')}
|
||||
type="text"
|
||||
label={t('modals.prompts.promptName')}
|
||||
className="h-10 rounded-lg"
|
||||
value={newPromptName}
|
||||
onChange={(e) => setNewPromptName(e.target.value)}
|
||||
/>
|
||||
<div className="relative top-[7px] left-3">
|
||||
<span className="bg-white px-1 text-xs text-silver dark:bg-[#26272E] dark:text-silver">
|
||||
{t('modals.prompts.promptText')}
|
||||
</span>
|
||||
</div>
|
||||
<label htmlFor="new-prompt-content" className="sr-only">
|
||||
Prompt Text
|
||||
</label>
|
||||
<textarea
|
||||
id="new-prompt-content"
|
||||
className="h-56 w-full rounded-lg border-2 border-silver px-3 py-2 outline-none dark:border-silver/40 dark:bg-transparent dark:text-white"
|
||||
value={newPromptContent}
|
||||
onChange={(e) => setNewPromptContent(e.target.value)}
|
||||
aria-label="Prompt Text"
|
||||
></textarea>
|
||||
</div>
|
||||
<div className="mt-6 flex flex-row-reverse">
|
||||
<button
|
||||
onClick={handleAddPrompt}
|
||||
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:opacity-90"
|
||||
disabled={disableSave}
|
||||
title={
|
||||
disableSave && newPromptName ? t('modals.prompts.nameExists') : ''
|
||||
}
|
||||
>
|
||||
{t('modals.prompts.save')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -114,16 +97,7 @@ function EditPrompt({
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<button
|
||||
className="absolute top-3 right-4 m-2 w-3"
|
||||
onClick={() => {
|
||||
setModalState('INACTIVE');
|
||||
}}
|
||||
aria-label="Close edit prompt modal"
|
||||
>
|
||||
<img className="filter dark:invert" src={Exit} alt="Close modal" />
|
||||
</button>
|
||||
<div>
|
||||
<div className="p-8">
|
||||
<p className="mb-1 text-xl text-jet dark:text-bright-gray">
|
||||
{t('modals.prompts.editPrompt')}
|
||||
@@ -271,15 +245,19 @@ export default function PromptsModal({
|
||||
} else {
|
||||
view = <></>;
|
||||
}
|
||||
return (
|
||||
<article
|
||||
className={`${
|
||||
modalState === 'ACTIVE' ? 'visible' : 'hidden'
|
||||
} fixed top-0 left-0 z-30 h-screen w-screen bg-gray-alpha`}
|
||||
|
||||
return modalState === 'ACTIVE' ? (
|
||||
<WrapperModal
|
||||
close={() => {
|
||||
setModalState('INACTIVE');
|
||||
if (type === 'ADD') {
|
||||
setNewPromptName('');
|
||||
setNewPromptContent('');
|
||||
}
|
||||
}}
|
||||
className="sm:w-[512px] mt-24"
|
||||
>
|
||||
<article className="mx-auto mt-24 flex w-[90vw] max-w-lg flex-col gap-4 rounded-2xl bg-white shadow-lg dark:bg-outer-space">
|
||||
{view}
|
||||
</article>
|
||||
</article>
|
||||
);
|
||||
{view}
|
||||
</WrapperModal>
|
||||
) : null;
|
||||
}
|
||||
|
||||
@@ -92,8 +92,10 @@ export default function Analytics() {
|
||||
const [loadingMessages, setLoadingMessages] = useLoaderState(true);
|
||||
const [loadingTokens, setLoadingTokens] = useLoaderState(true);
|
||||
const [loadingFeedback, setLoadingFeedback] = useLoaderState(true);
|
||||
const [loadingChatbots, setLoadingChatbots] = useLoaderState(true);
|
||||
|
||||
const fetchChatbots = async () => {
|
||||
setLoadingChatbots(true);
|
||||
try {
|
||||
const response = await userService.getAPIKeys();
|
||||
if (!response.ok) {
|
||||
@@ -103,6 +105,8 @@ export default function Analytics() {
|
||||
setChatbots(chatbots);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
} finally {
|
||||
setLoadingChatbots(false);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -188,6 +192,7 @@ export default function Analytics() {
|
||||
return (
|
||||
<div className="mt-12">
|
||||
<div className="flex flex-col items-start">
|
||||
|
||||
<div className="flex flex-col gap-3">
|
||||
<p className="font-bold text-jet dark:text-bright-gray">
|
||||
{t('settings.analytics.filterByChatbot')}
|
||||
@@ -218,6 +223,41 @@ export default function Analytics() {
|
||||
border="border"
|
||||
/>
|
||||
</div>
|
||||
{loadingChatbots ? (
|
||||
<SkeletonLoader component="dropdown" />
|
||||
) : (
|
||||
<div className="flex flex-col gap-3">
|
||||
<p className="font-bold text-jet dark:text-bright-gray">
|
||||
{t('settings.analytics.filterByChatbot')}
|
||||
</p>
|
||||
<Dropdown
|
||||
size="w-[55vw] sm:w-[360px]"
|
||||
options={[
|
||||
...chatbots.map((chatbot) => ({
|
||||
label: chatbot.name,
|
||||
value: chatbot.id,
|
||||
})),
|
||||
{ label: t('settings.analytics.none'), value: '' },
|
||||
]}
|
||||
placeholder={t('settings.analytics.selectChatbot')}
|
||||
onSelect={(chatbot: { label: string; value: string }) => {
|
||||
setSelectedChatbot(
|
||||
chatbots.find((item) => item.id === chatbot.value),
|
||||
);
|
||||
}}
|
||||
selectedValue={
|
||||
(selectedChatbot && {
|
||||
label: selectedChatbot.name,
|
||||
value: selectedChatbot.id,
|
||||
}) ||
|
||||
null
|
||||
}
|
||||
rounded="3xl"
|
||||
border="border"
|
||||
borderColor="gray-700"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Messages Analytics */}
|
||||
<div className="mt-8 w-full flex flex-col [@media(min-width:1080px)]:flex-row gap-3">
|
||||
|
||||
@@ -168,7 +168,7 @@ export default function Prompts({
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="mt-[24px] rounded-3xl border border-solid border-purple-700 px-5 py-3 text-purple-700 transition-colors hover:bg-purple-700 hover:text-white dark:border-purple-400 dark:text-purple-400 dark:hover:bg-purple-400 dark:hover:text-white"
|
||||
className="mt-[24px] rounded-3xl border border-solid border-purple-30 px-5 py-3 text-purple-30 transition-colors hover:text-white hover:bg-[#6F3FD1] dark:border-purple-30 dark:text-purple-30 dark:hover:bg-purple-30 dark:hover:text-white"
|
||||
onClick={() => {
|
||||
setModalType('ADD');
|
||||
setModalState('ACTIVE');
|
||||
|
||||
@@ -101,7 +101,7 @@ function Upload({
|
||||
borderVariant="thin"
|
||||
label={field.label}
|
||||
required={isRequired}
|
||||
colorVariant="gray"
|
||||
colorVariant="silver"
|
||||
/>
|
||||
);
|
||||
case 'number':
|
||||
@@ -123,7 +123,7 @@ function Upload({
|
||||
borderVariant="thin"
|
||||
label={field.label}
|
||||
required={isRequired}
|
||||
colorVariant="gray"
|
||||
colorVariant="silver"
|
||||
/>
|
||||
);
|
||||
case 'enum':
|
||||
@@ -572,13 +572,13 @@ function Upload({
|
||||
</p>
|
||||
{!activeTab && (
|
||||
<div>
|
||||
<p className="text-gray-6000 dark:text-bright-gray text-sm text-center font-medium">
|
||||
<p className="dark text-gray-6000 dark:text-bright-gray text-sm text-center font-medium">
|
||||
{t('modals.uploadDoc.select')}
|
||||
</p>
|
||||
<div className="w-full gap-4 h-full p-4 flex flex-col md:flex-row md:gap-4 justify-center items-center">
|
||||
<button
|
||||
onClick={() => setActiveTab('file')}
|
||||
className="opacity-85 hover:opacity-100 rounded-3xl text-sm font-medium border flex flex-col items-center justify-center hover:shadow-purple-30/30 hover:shadow-lg p-8 gap-4 bg-white text-[#777777] dark:bg-outer-space dark:text-[#c3c3c3] hover:border-purple-30 border-[#D7D7D7] h-40 w-40 md:w-52 md:h-52"
|
||||
className="opacity-85 hover:opacity-100 rounded-3xl text-sm font-medium border flex flex-col items-center justify-center hover:shadow-purple-30/30 hover:shadow-lg p-8 gap-4 bg-transparent text-[#777777] dark:bg-transparent dark:text-[#c3c3c3] hover:border-purple-30 border-[#D7D7D7] h-40 w-40 md:w-52 md:h-52"
|
||||
>
|
||||
<img
|
||||
src={FileUpload}
|
||||
@@ -588,7 +588,7 @@ function Upload({
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setActiveTab('remote')}
|
||||
className="opacity-85 hover:opacity-100 rounded-3xl text-sm font-medium border flex flex-col items-center justify-center hover:shadow-purple-30/30 hover:shadow-lg p-8 gap-4 bg-white text-[#777777] dark:bg-outer-space dark:text-[#c3c3c3] hover:border-purple-30 border-[#D7D7D7] h-40 w-40 md:w-52 md:h-52"
|
||||
className="opacity-85 hover:opacity-100 rounded-3xl text-sm font-medium border flex flex-col items-center justify-center hover:shadow-purple-30/30 hover:shadow-lg p-8 gap-4 bg-transparent text-[#777777] dark:bg-transparent dark:text-[#c3c3c3] hover:border-purple-30 border-[#D7D7D7] h-40 w-40 md:w-52 md:h-52"
|
||||
>
|
||||
<img
|
||||
src={WebsiteCollect}
|
||||
@@ -604,18 +604,16 @@ function Upload({
|
||||
<>
|
||||
<Input
|
||||
type="text"
|
||||
colorVariant="gray"
|
||||
colorVariant="silver"
|
||||
value={docName}
|
||||
onChange={(e) => setDocName(e.target.value)}
|
||||
borderVariant="thin"
|
||||
></Input>
|
||||
<div className="relative bottom-12 left-2 mt-[-20px]">
|
||||
<span className="bg-white px-2 text-xs text-gray-4000 dark:bg-outer-space dark:text-silver">
|
||||
{t('modals.uploadDoc.name')}
|
||||
</span>
|
||||
</div>
|
||||
<div {...getRootProps()}>
|
||||
<span className="rounded-3xl border border-purple-30 px-4 py-2 font-medium text-purple-30 hover:cursor-pointer dark:bg-purple-taupe dark:text-silver">
|
||||
placeholder={t('modals.uploadDoc.name')}
|
||||
label={t('modals.uploadDoc.name')}
|
||||
required={true}
|
||||
/>
|
||||
<div className="my-2" {...getRootProps()}>
|
||||
<span className="rounded-3xl bg-transparent px-4 py-2 font-medium text-purple-30 hover:cursor-pointer dark:text-silver border border-[#7F7F82]">
|
||||
<input type="button" {...getInputProps()} />
|
||||
{t('modals.uploadDoc.choose')}
|
||||
</span>
|
||||
@@ -624,7 +622,7 @@ function Upload({
|
||||
{t('modals.uploadDoc.info')}
|
||||
</p>
|
||||
<div className="mt-0 max-w-full">
|
||||
<p className="mb-[14px] font-medium text-eerie-black dark:text-light-gray">
|
||||
<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">
|
||||
@@ -638,7 +636,7 @@ function Upload({
|
||||
</p>
|
||||
))}
|
||||
{files.length === 0 && (
|
||||
<p className="text-gray-6000 dark:text-light-gray">
|
||||
<p className="text-[14px] text-gray-6000 dark:text-light-gray">
|
||||
{t('none')}
|
||||
</p>
|
||||
)}
|
||||
@@ -649,7 +647,7 @@ function Upload({
|
||||
{activeTab === 'remote' && (
|
||||
<>
|
||||
<Dropdown
|
||||
border="border-2"
|
||||
border="border"
|
||||
options={urlOptions}
|
||||
selectedValue={
|
||||
urlOptions.find((opt) => opt.value === ingestor.type) || null
|
||||
@@ -664,7 +662,7 @@ function Upload({
|
||||
|
||||
<Input
|
||||
type="text"
|
||||
colorVariant="gray"
|
||||
colorVariant="silver"
|
||||
value={remoteName}
|
||||
onChange={(e) => setRemoteName(e.target.value)}
|
||||
borderVariant="thin"
|
||||
@@ -687,11 +685,11 @@ function Upload({
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<div className="flex justify-between">
|
||||
<div className="flex justify-end gap-4">
|
||||
{activeTab && (
|
||||
<button
|
||||
onClick={() => setActiveTab(null)}
|
||||
className="rounded-3xl border border-purple-30 px-4 py-2 font-medium text-purple-30 hover:cursor-pointer dark:bg-purple-taupe dark:text-silver"
|
||||
className="rounded-3xl bg-transparent px-4 py-2 font-medium text-purple-30 hover:cursor-pointer dark:text-silver text-[14px]"
|
||||
>
|
||||
{t('modals.uploadDoc.back')}
|
||||
</button>
|
||||
@@ -706,7 +704,7 @@ function Upload({
|
||||
}
|
||||
}}
|
||||
disabled={isUploadDisabled()}
|
||||
className={`rounded-3xl px-4 py-2 font-medium ${
|
||||
className={`rounded-3xl px-4 py-2 font-medium text-[14px] ${
|
||||
isUploadDisabled()
|
||||
? 'cursor-not-allowed bg-gray-300 text-gray-500'
|
||||
: 'cursor-pointer bg-purple-30 text-white hover:bg-purple-40'
|
||||
|
||||
Reference in New Issue
Block a user