Merge branch 'arc53:main' into AidanComponentEditChat

This commit is contained in:
aidanbennettjones
2025-02-21 15:02:39 -05:00
committed by GitHub
32 changed files with 742 additions and 851 deletions

View File

@@ -85,7 +85,6 @@ class OpenAILLM(BaseLLM):
**kwargs,
):
messages = self._clean_messages_openai(messages)
print(messages)
if tools:
response = self.client.chat.completions.create(
model=model,

View File

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

View File

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

View File

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

View File

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

View File

@@ -91,18 +91,18 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
);
const renderLogs = () => (
<div className="animate-pulse space-y-px">
<div className="w-full animate-pulse space-y-px">
{[...Array(8)].map((_, idx) => (
<div
key={idx}
className="flex items-start p-2 hover:bg-[#F9F9F9] hover:dark:bg-dark-charcoal"
className="w-full flex items-start p-2 hover:bg-[#F9F9F9] hover:dark:bg-dark-charcoal"
>
<div className="flex items-center gap-2">
<div className="w-3 h-3 bg-gray-300 dark:bg-gray-600 rounded-sm"></div>
<div className="flex flex-row items-center gap-2">
<div className="h-3 bg-gray-300 dark:bg-gray-600 rounded w-20"></div>
<div className="h-3 bg-gray-300 dark:bg-gray-600 rounded w-14 bg-opacity-80"></div>
<div className="h-3 bg-gray-300 dark:bg-gray-600 rounded w-40"></div>
<div className="w-full flex items-center gap-2">
<div className="w-3 h-3 bg-gray-300 dark:bg-gray-600 rounded-lg"></div>
<div className="w-full flex flex-row items-center gap-2">
<div className="h-3 bg-gray-300 dark:bg-gray-600 rounded-lg w-[30%] lg:w-52"></div>
<div className="h-3 bg-gray-300 dark:bg-gray-600 rounded-lg w-[16%] lg:w-28"></div>
<div className="h-3 bg-gray-300 dark:bg-gray-600 rounded-lg w-[40%] lg:w-64"></div>
</div>
</div>
</div>

View File

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

View File

@@ -642,47 +642,46 @@ function ToolCalls({ toolCalls }: { toolCalls: ToolCallsType[] }) {
title={`${toolCall.tool_name} - ${toolCall.action_name.substring(0, toolCall.action_name.lastIndexOf('_'))}`}
className="w-full rounded-[20px] bg-gray-1000 dark:bg-gun-metal hover:bg-[#F1F1F1] dark:hover:bg-[#2C2E3C]"
titleClassName="px-6 py-2 text-sm font-semibold"
children={
<div className="flex flex-col gap-1">
<div className="flex flex-col border border-silver dark:border-silver/20 rounded-2xl">
<p className="flex flex-row items-center justify-between px-2 py-1 text-sm font-semibold bg-black/10 dark:bg-[#191919] rounded-t-2xl break-words">
<span style={{ fontFamily: 'IBMPlexMono-Medium' }}>
Arguments
</span>{' '}
<CopyButton
text={JSON.stringify(toolCall.arguments, null, 2)}
/>
</p>
<p className="p-2 font-mono text-sm dark:tex dark:bg-[#222327] rounded-b-2xl break-words">
<span
className="text-black dark:text-gray-400 leading-[23px]"
style={{ fontFamily: 'IBMPlexMono-Medium' }}
>
{JSON.stringify(toolCall.arguments, null, 2)}
</span>
</p>
</div>
<div className="flex flex-col border border-silver dark:border-silver/20 rounded-2xl">
<p className="flex flex-row items-center justify-between px-2 py-1 text-sm font-semibold bg-black/10 dark:bg-[#191919] rounded-t-2xl break-words">
<span style={{ fontFamily: 'IBMPlexMono-Medium' }}>
Response
</span>{' '}
<CopyButton
text={JSON.stringify(toolCall.result, null, 2)}
/>
</p>
<p className="p-2 font-mono text-sm dark:tex dark:bg-[#222327] rounded-b-2xl break-words">
<span
className="text-black dark:text-gray-400 leading-[23px]"
style={{ fontFamily: 'IBMPlexMono-Medium' }}
>
{JSON.stringify(toolCall.result, null, 2)}
</span>
</p>
</div>
>
<div className="flex flex-col gap-1">
<div className="flex flex-col border border-silver dark:border-silver/20 rounded-2xl">
<p className="flex flex-row items-center justify-between px-2 py-1 text-sm font-semibold bg-black/10 dark:bg-[#191919] rounded-t-2xl break-words">
<span style={{ fontFamily: 'IBMPlexMono-Medium' }}>
Arguments
</span>{' '}
<CopyButton
text={JSON.stringify(toolCall.arguments, null, 2)}
/>
</p>
<p className="p-2 font-mono text-sm dark:tex dark:bg-[#222327] rounded-b-2xl break-words">
<span
className="text-black dark:text-gray-400 leading-[23px]"
style={{ fontFamily: 'IBMPlexMono-Medium' }}
>
{JSON.stringify(toolCall.arguments, null, 2)}
</span>
</p>
</div>
}
/>
<div className="flex flex-col border border-silver dark:border-silver/20 rounded-2xl">
<p className="flex flex-row items-center justify-between px-2 py-1 text-sm font-semibold bg-black/10 dark:bg-[#191919] rounded-t-2xl break-words">
<span style={{ fontFamily: 'IBMPlexMono-Medium' }}>
Response
</span>{' '}
<CopyButton
text={JSON.stringify(toolCall.result, null, 2)}
/>
</p>
<p className="p-2 font-mono text-sm dark:tex dark:bg-[#222327] rounded-b-2xl break-words">
<span
className="text-black dark:text-gray-400 leading-[23px]"
style={{ fontFamily: 'IBMPlexMono-Medium' }}
>
{JSON.stringify(toolCall.result, null, 2)}
</span>
</p>
</div>
</div>
</Accordion>
))}
</div>
</div>

View File

@@ -81,7 +81,8 @@
"sourceDoc": "Source Document",
"createNew": "Create New",
"noData": "No existing Chatbots",
"deleteConfirmation": "Are you sure you want to delete the API key '{{name}}'?"
"deleteConfirmation": "Are you sure you want to delete the API key '{{name}}'?",
"description": "Here you can create and manage your chatbots. Chatbots can be deployed to websites as widgets or used inside your applications."
},
"analytics": {
"label": "Analytics",

View File

@@ -81,7 +81,8 @@
"sourceDoc": "Documento Fuente",
"createNew": "Crear Nuevo",
"noData": "No hay chatbots existentes",
"deleteConfirmation": "¿Estás seguro de que quieres eliminar la clave API '{{name}}'?"
"deleteConfirmation": "¿Estás seguro de que quieres eliminar la clave API '{{name}}'?",
"description": "Aquí puede crear y gestionar sus chatbots. Los chatbots se pueden implementar en sitios web como widgets o utilizarse dentro de sus aplicaciones."
},
"analytics": {
"label": "Analítica",

View File

@@ -80,7 +80,8 @@
"sourceDoc": "ソースドキュメント",
"createNew": "新規作成",
"noData": "既存のチャットボットはありません",
"deleteConfirmation": "APIキー '{{name}}' を削除してもよろしいですか?"
"deleteConfirmation": "APIキー '{{name}}' を削除してもよろしいですか?",
"description": "ここでチャットボットを作成・管理できます。チャットボットはウィジェットとしてウェブサイトに導入したり、アプリケーション内で使用したりすることができます。"
},
"analytics": {
"label": "分析",

View File

@@ -81,7 +81,8 @@
"sourceDoc": "Источник документа",
"createNew": "Создать новый",
"noData": "Нет существующих чатботов",
"deleteConfirmation": "Вы уверены, что хотите удалить API ключ '{{name}}'?"
"deleteConfirmation": "Вы уверены, что хотите удалить API ключ '{{name}}'?",
"description": "Здесь вы можете создавать и управлять чат-ботами. Чат-боты могут быть развернуты на веб-сайтах в виде виджетов или использоваться внутри ваших приложений."
},
"analytics": {
"label": "Аналитика",

View File

@@ -81,7 +81,8 @@
"sourceDoc": "來源文件",
"createNew": "建立新的",
"noData": "沒有現有的聊天機器人",
"deleteConfirmation": "您確定要刪除 API 金鑰 '{{name}}' 嗎?"
"deleteConfirmation": "您確定要刪除 API 金鑰 '{{name}}' 嗎?",
"description": "在這裡,您可以創建和管理您的聊天機器人。聊天機器人可以作為小部件部署到網站上,或在您的應用程序中使用。"
},
"analytics": {
"label": "分析",

View File

@@ -81,7 +81,8 @@
"sourceDoc": "源文档",
"createNew": "创建新的",
"noData": "没有现有的聊天机器人",
"deleteConfirmation": "您确定要删除 API 密钥 '{{name}}' 吗?"
"deleteConfirmation": "您确定要删除 API 密钥 '{{name}}' 吗?",
"description": "在这里,您可以创建和管理您的聊天机器人。聊天机器人可以作为小部件部署到网站上,或在您的应用程序中使用。"
},
"analytics": {
"label": "分析",

View File

@@ -1,21 +1,21 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import Exit from '../assets/exit.svg';
import Input from '../components/Input';
import { ActiveState } from '../models/misc';
import WrapperModal from './WrapperModal';
type AddActionModalProps = {
modalState: ActiveState;
setModalState: (state: ActiveState) => void;
handleSubmit: (actionName: string) => void;
};
const isValidFunctionName = (name: string): boolean => {
const pattern = /^[a-zA-Z0-9_-]+$/;
return pattern.test(name);
};
interface AddActionModalProps {
modalState: ActiveState;
setModalState: (state: ActiveState) => void;
handleSubmit: (actionName: string) => void;
}
export default function AddActionModal({
modalState,
setModalState,
@@ -23,79 +23,73 @@ export default function AddActionModal({
}: AddActionModalProps) {
const { t } = useTranslation();
const [actionName, setActionName] = React.useState('');
const [functionNameError, setFunctionNameError] = useState<boolean>(false); // New error state
const [functionNameError, setFunctionNameError] = useState<boolean>(false);
const handleAddAction = () => {
if (!isValidFunctionName(actionName)) {
setFunctionNameError(true); // Set error state if invalid
setFunctionNameError(true);
return;
}
setFunctionNameError(false); // Clear error state if valid
setFunctionNameError(false);
handleSubmit(actionName);
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">
<button
className="absolute top-3 right-4 m-2 w-3"
onClick={() => {
setModalState('INACTIVE');
<div>
<h2 className="font-semibold text-xl text-jet dark:text-bright-gray px-3">
New Action
</h2>
<div className="mt-6 relative px-3">
<span className="z-10 absolute left-5 -top-2 bg-white px-2 text-xs text-gray-4000 dark:bg-[#26272E] dark:text-silver">
Action Name
</span>
<Input
type="text"
value={actionName}
onChange={(e) => {
const value = e.target.value;
setActionName(value);
setFunctionNameError(!isValidFunctionName(value));
}}
borderVariant="thin"
placeholder={'Enter name'}
/>
<p
className={`mt-2 ml-1 text-xs italic ${
functionNameError ? 'text-red-500' : 'text-gray-500'
}`}
>
<img className="filter dark:invert" src={Exit} />
</button>
<div className="p-6">
<h2 className="font-semibold text-xl text-jet dark:text-bright-gray px-3">
New Action
</h2>
<div className="mt-6 relative px-3">
<span className="z-10 absolute left-5 -top-2 bg-white px-2 text-xs text-gray-4000 dark:bg-[#26272E] dark:text-silver">
Action Name
</span>
<Input
type="text"
value={actionName}
onChange={(e) => setActionName(e.target.value)}
borderVariant="thin"
placeholder={'Enter 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
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>
</div>
{functionNameError
? 'Invalid function name format. Use only letters, numbers, underscores, and hyphens.'
: 'Use only letters, numbers, underscores, and hyphens (e.g., `get_data`, `send_report`, etc.)'}
</p>
</div>
</article>
</div>
<div className="mt-3 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
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"
>
{t('modals.configTool.closeButton')}
</button>
</div>
</div>
</WrapperModal>
);
}

View File

@@ -2,11 +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,
@@ -21,6 +22,8 @@ export default function AddToolModal({
getUserTools: () => void;
onToolAdded: (toolId: string) => void;
}) {
const { t } = useTranslation();
const modalRef = useRef<HTMLDivElement>(null);
const [availableTools, setAvailableTools] = React.useState<
AvailableToolType[]
>([]);
@@ -28,8 +31,7 @@ export default function AddToolModal({
React.useState<AvailableToolType | null>(null);
const [configModalState, setConfigModalState] =
React.useState<ActiveState>('INACTIVE');
const modalRef = useRef<HTMLDivElement>(null);
const { t } = useTranslation();
const [loading, setLoading] = React.useState(false);
useOutsideAlerter(modalRef, () => {
if (modalState === 'ACTIVE') {
@@ -38,6 +40,7 @@ export default function AddToolModal({
}, [modalState]);
const getAvailableTools = () => {
setLoading(true);
userService
.getAvailableTools()
.then((res) => {
@@ -45,6 +48,7 @@ export default function AddToolModal({
})
.then((data) => {
setAvailableTools(data.data);
setLoading(false);
});
};
@@ -85,76 +89,68 @@ export default function AddToolModal({
React.useEffect(() => {
if (modalState === 'ACTIVE') getAvailableTools();
}, [modalState]);
return (
<>
<div
className={`${
modalState === 'ACTIVE' ? 'visible' : 'hidden'
} fixed top-0 left-0 z-30 h-screen w-screen bg-gray-alpha flex items-center justify-center`}
>
<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="p-6">
<div className="flex flex-col gap-4 h-full">
<div>
<h2 className="font-semibold text-xl text-jet dark:text-bright-gray px-3">
{t('settings.tools.selectToolSetup')}
</h2>
<div className="mt-5 flex flex-col sm:grid sm:grid-cols-3 gap-4 h-[73vh] overflow-auto px-3 py-px">
{availableTools.map((tool, index) => (
<div
role="button"
tabIndex={0}
key={index}
className="h-52 w-full p-6 border rounded-2xl border-silver dark:border-[#4D4E58] flex flex-col justify-between dark:bg-[#32333B] cursor-pointer"
onClick={() => {
setSelectedTool(tool);
handleAddTool(tool);
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
{loading ? (
<div className="h-full col-span-3 flex items-center justify-center">
<Spinner />
</div>
) : (
availableTools.map((tool, index) => (
<div
role="button"
tabIndex={0}
key={index}
className="h-52 w-full p-6 border rounded-2xl border-silver dark:border-[#4D4E58] flex flex-col justify-between dark:bg-[#32333B] cursor-pointer hover:border-[#9d9d9d] hover:dark:border-[#717179]"
onClick={() => {
setSelectedTool(tool);
handleAddTool(tool);
}
}}
>
<div className="w-full">
<div className="px-1 w-full flex items-center justify-between">
<img
src={`/toolIcons/tool_${tool.name}.svg`}
className="h-8 w-8"
/>
</div>
<div className="mt-[9px]">
<p className="px-1 text-sm font-semibold text-eerie-black dark:text-white leading-relaxed capitalize">
{tool.displayName}
</p>
<p className="mt-1 px-1 h-24 overflow-auto text-sm text-gray-600 dark:text-[#8a8a8c] leading-relaxed">
{tool.description}
</p>
}}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
setSelectedTool(tool);
handleAddTool(tool);
}
}}
>
<div className="w-full">
<div className="px-1 w-full flex items-center justify-between">
<img
src={`/toolIcons/tool_${tool.name}.svg`}
className="h-8 w-8"
/>
</div>
<div className="mt-[9px]">
<p
title={tool.displayName}
className="px-1 text-sm font-semibold text-eerie-black dark:text-white leading-relaxed capitalize truncate"
>
{tool.displayName}
</p>
<p className="mt-1 px-1 h-24 overflow-auto text-sm text-gray-600 dark:text-[#8a8a8c] leading-relaxed">
{tool.description}
</p>
</div>
</div>
</div>
</div>
))}
))
)}
</div>
</div>
</div>
</article>
</div>
</WrapperComponent>
)}
<ConfigToolModal
modalState={configModalState}
setModalState={setConfigModalState}

View File

@@ -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,63 +38,47 @@ 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">
<button
className="absolute top-3 right-4 m-2 w-3"
onClick={() => {
setModalState('INACTIVE');
}}
>
<img className="filter dark:invert" src={Exit} />
</button>
<div className="p-6">
<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 relative px-3">
<span className="absolute left-5 -top-2 bg-white px-2 text-xs text-gray-4000 dark:bg-[#26272E] dark:text-silver">
{t('modals.configTool.apiKeyLabel')}
</span>
<Input
type="text"
value={authKey}
onChange={(e) => setAuthKey(e.target.value)}
borderVariant="thin"
placeholder={t('modals.configTool.apiKeyPlaceholder')}
></Input>
</div>
<div className="mt-8 flex flex-row-reverse gap-1 px-3">
<button
onClick={() => {
handleAddTool(tool as AvailableToolType);
}}
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]"
>
{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>
</div>
<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>
</article>
</div>
<div className="mt-8 flex flex-row-reverse gap-1 px-3">
<button
onClick={() => {
tool && handleAddTool(tool);
}}
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-[#6F3FD1]"
>
{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>
</div>
</WrapperModal>
);
}

View File

@@ -32,7 +32,7 @@ export default function ConfirmationModal({
>
<div className="relative">
<div>
<p className="font-base mb-1 w-[90%] text-lg text-jet dark:text-bright-gray">
<p className="font-base mb-1 w-[90%] text-lg break-words text-jet dark:text-bright-gray">
{message}
</p>
<div>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -98,111 +98,132 @@ export default function APIKeys() {
}, []);
return (
<div className="mt-8">
<div className="flex flex-col max-w-[876px]">
<div className="flex justify-end">
<div className="flex flex-col w-full mt-8 max-w-full overflow-hidden">
<div className="flex flex-col relative flex-grow">
<div className="mb-6">
<h2 className="text-base font-medium text-sonic-silver">
{t('settings.apiKeys.description')}
</h2>
</div>
<div className="mb-6 flex flex-col sm:flex-row justify-end items-start sm:items-center gap-3">
<button
onClick={() => setCreateModal(true)}
className="rounded-full bg-purple-30 px-4 py-3 text-white hover:bg-[#6F3FD1]"
className="rounded-full w-full sm:w-40 bg-purple-30 px-4 py-3 text-white hover:bg-[#6F3FD1]"
title={t('settings.apiKeys.createNew')}
>
{t('settings.apiKeys.createNew')}
</button>
</div>
{isCreateModalOpen && (
<CreateAPIKeyModal
createAPIKey={handleCreateKey}
close={() => setCreateModal(false)}
/>
)}
{isSaveKeyModalOpen && (
<SaveAPIKeyModal
apiKey={newKey}
close={() => setSaveKeyModal(false)}
/>
)}
{keyToDelete && (
<ConfirmationModal
message={t('settings.apiKeys.deleteConfirmation', {
name: keyToDelete.name,
})}
modalState="ACTIVE"
setModalState={() => setKeyToDelete(null)}
submitLabel={t('modals.deleteConv.delete')}
handleSubmit={() => handleDeleteKey(keyToDelete.id)}
handleCancel={() => setKeyToDelete(null)}
/>
)}
<div className="mt-[27px] w-full">
<div className="w-full overflow-x-auto">
<div className="flex flex-col">
<div className="flex-grow">
<div className="dark:border-silver/40 border-silver rounded-md border overflow-auto">
<table className="min-w-full divide-y divide-silver dark:divide-silver/40">
<thead>
<tr className="text-start text-sm font-medium text-gray-700 dark:text-gray-50 uppercase">
<th scope="col" className="p-2">
{t('settings.apiKeys.name')}
</th>
<th scope="col" className="p-2">
{t('settings.apiKeys.sourceDoc')}
</th>
<th scope="col" className="p-2">
{t('settings.apiKeys.key')}
</th>
<th
scope="col"
className="p-2"
aria-label="Actions"
></th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 dark:divide-neutral-700">
{loading ? (
<SkeletonLoader component="chatbot" />
) : !apiKeys?.length ? (
<tr>
<td
colSpan={4}
className="!p-4 text-gray-800 dark:text-neutral-200 text-center"
>
{t('settings.apiKeys.noData')}
</td>
</tr>
) : (
Array.isArray(apiKeys) &&
apiKeys.map((element, index) => (
<tr
key={element.id}
className="text-nowrap whitespace-nowrap text-center text-sm font-medium text-gray-800 dark:text-neutral-200 p-2"
>
<td className="p-2">{element.name}</td>
<td className="p-2">{element.source}</td>
<td className="p-2">{element.key}</td>
<td className="p-2">
<div className="relative w-full">
<div className="border rounded-md border-gray-300 dark:border-silver/40 overflow-hidden">
<div className="overflow-x-auto table-scroll">
<table className="w-full table-auto">
<thead>
<tr className="border-b border-gray-300 dark:border-silver/40">
<th className="py-3 px-4 text-left text-xs font-medium text-sonic-silver uppercase w-[35%]">
{t('settings.apiKeys.name')}
</th>
<th className="py-3 px-4 text-left text-xs font-medium text-sonic-silver uppercase w-[35%]">
{t('settings.apiKeys.sourceDoc')}
</th>
<th className="py-3 px-4 text-left text-xs font-medium text-sonic-silver uppercase w-[25%]">
<span className="hidden sm:inline">
{t('settings.apiKeys.key')}
</span>
<span className="sm:hidden">
{t('settings.apiKeys.key')}
</span>
</th>
<th className="py-3 px-4 text-right text-xs font-medium text-gray-700 dark:text-[#E0E0E0] uppercase w-[5%]">
<span className="sr-only">Actions</span>
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-300 dark:divide-silver/40">
{loading ? (
<SkeletonLoader component="table" />
) : !apiKeys?.length ? (
<tr>
<td
colSpan={4}
className="py-4 text-center text-gray-700 dark:text-neutral-200 bg-transparent"
>
{t('settings.apiKeys.noData')}
</td>
</tr>
) : (
Array.isArray(apiKeys) &&
apiKeys.map((element) => (
<tr
key={element.id}
className="group transition-colors hover:bg-gray-50 dark:hover:bg-gray-800/50"
>
<td className="py-4 px-4 text-sm text-gray-700 dark:text-[#E0E0E0] w-[35%] min-w-48 max-w-0">
<div className="truncate" title={element.name}>
{element.name}
</div>
</td>
<td className="py-4 px-4 text-sm text-gray-700 dark:text-[#E0E0E0] w-[35%] min-w-48 max-w-0">
<div className="truncate" title={element.source}>
{element.source}
</div>
</td>
<td className="py-4 px-4 text-sm font-mono text-gray-700 dark:text-[#E0E0E0] w-[25%]">
<div className="truncate" title={element.key}>
{element.key}
</div>
</td>
<td className="py-4 px-4 text-right w-[5%]">
<div className="flex justify-end">
<button
onClick={() =>
setKeyToDelete({
id: element.id,
name: element.name,
})
}
className="inline-flex items-center justify-center w-8 h-8 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors flex-shrink-0"
>
<img
src={Trash}
alt={`Delete ${element.name}`}
className="h-4 w-4 cursor-pointer hover:opacity-50 mx-auto"
id={`img-${index}`}
onClick={() =>
setKeyToDelete({
id: element.id,
name: element.name,
})
}
alt={t('convTile.delete')}
className="h-4 w-4 opacity-60 hover:opacity-100"
/>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</div>
</button>
</div>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</div>
</div>
</div>
{isCreateModalOpen && (
<CreateAPIKeyModal
createAPIKey={handleCreateKey}
close={() => setCreateModal(false)}
/>
)}
{isSaveKeyModalOpen && (
<SaveAPIKeyModal apiKey={newKey} close={() => setSaveKeyModal(false)} />
)}
{keyToDelete && (
<ConfirmationModal
message={t('settings.apiKeys.deleteConfirmation', {
name: keyToDelete.name,
})}
modalState="ACTIVE"
setModalState={() => setKeyToDelete(null)}
submitLabel={t('modals.deleteConv.delete')}
handleSubmit={() => handleDeleteKey(keyToDelete.id)}
handleCancel={() => setKeyToDelete(null)}
/>
)}
</div>
);
}

View File

@@ -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,37 +192,41 @@ 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')}
</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>
{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">

View File

@@ -176,14 +176,14 @@ export default function Documents({
}}
/>
) : (
<div className="flex flex-col mt-8">
<div className="flex flex-col mt-8 w-full max-w-full overflow-hidden">
<div className="flex flex-col relative flex-grow">
<div className="mb-6">
<h2 className="text-base font-medium text-sonic-silver">
{t('settings.documents.title')}
</h2>
</div>
<div className="my-3 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3">
<div className="mb-6 flex flex-col sm:flex-row justify-between items-start sm:items-center gap-3">
<div className="w-full sm:w-auto">
<label htmlFor="document-search-input" className="sr-only">
{t('settings.documents.searchPlaceholder')}
@@ -213,117 +213,121 @@ export default function Documents({
{t('settings.documents.addNew')}
</button>
</div>
<div className="flex flex-col flex-grow">
{' '}
<div className="relative w-full">
<div className="border rounded-md border-gray-300 dark:border-silver/40 overflow-hidden">
<table className="w-full min-w-[640px] table-auto">
<thead>
<tr className="border-b border-gray-300 dark:border-silver/40">
<th className="py-3 px-4 text-left text-xs font-medium text-sonic-silver uppercase w-[45%]">
{t('settings.documents.name')}
</th>
<th className="py-3 px-4 text-center text-xs font-medium text-sonic-silver uppercase w-[20%]">
<div className="flex justify-center items-center">
{t('settings.documents.date')}
<img
className="cursor-pointer ml-2"
onClick={() => refreshDocs('date')}
src={caretSort}
alt="sort"
/>
</div>
</th>
<th className="py-3 px-4 text-center text-xs font-medium text-sonic-silver uppercase w-[25%]">
<div className="flex justify-center items-center">
<span className="hidden sm:inline">
{t('settings.documents.tokenUsage')}
<div className="overflow-x-auto table-scroll">
<table className="w-full table-auto">
<thead>
<tr className="border-b border-gray-300 dark:border-silver/40">
<th className="py-3 px-4 text-left text-xs font-medium text-sonic-silver uppercase w-[45%]">
{t('settings.documents.name')}
</th>
<th className="py-3 px-4 text-center text-xs font-medium text-sonic-silver uppercase w-[20%]">
<div className="flex justify-center items-center">
{t('settings.documents.date')}
<img
className="cursor-pointer ml-2"
onClick={() => refreshDocs('date')}
src={caretSort}
alt="sort"
/>
</div>
</th>
<th className="py-3 px-4 text-center text-xs font-medium text-sonic-silver uppercase w-[25%]">
<div className="flex justify-center items-center">
<span className="hidden sm:inline">
{t('settings.documents.tokenUsage')}
</span>
<span className="sm:hidden">
{t('settings.documents.tokenUsage')}
</span>
<img
className="cursor-pointer ml-2"
onClick={() => refreshDocs('tokens')}
src={caretSort}
alt="sort"
/>
</div>
</th>
<th className="py-3 px-4 text-right text-xs font-medium text-gray-700 dark:text-[#E0E0E0] uppercase w-[10%]">
<span className="sr-only">
{t('settings.documents.actions')}
</span>
<span className="sm:hidden">
{t('settings.documents.tokenUsage')}
</span>
<img
className="cursor-pointer ml-2"
onClick={() => refreshDocs('tokens')}
src={caretSort}
alt="sort"
/>
</div>
</th>
<th className="py-3 px-4 text-right text-xs font-medium text-gray-700 dark:text-[#E0E0E0] uppercase w-[10%]">
<span className="sr-only">
{t('settings.documents.actions')}
</span>
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-300 dark:divide-silver/40">
{loading ? (
<SkeletonLoader component="table" />
) : !currentDocuments?.length ? (
<tr>
<td
colSpan={4}
className="py-4 text-center text-gray-700 dark:text-neutral-200 bg-transparent"
>
{t('settings.documents.noData')}
</td>
</th>
</tr>
) : (
currentDocuments.map((document, index) => (
<tr
key={index}
className="group transition-colors"
onClick={() => setShowDocumentChunks(document)}
>
</thead>
<tbody className="divide-y divide-gray-300 dark:divide-silver/40">
{loading ? (
<SkeletonLoader component="table" />
) : !currentDocuments?.length ? (
<tr>
<td
className="py-4 px-4 text-sm text-gray-700 dark:text-[#E0E0E0] w-[45%] truncate group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50"
title={document.name}
colSpan={4}
className="py-4 text-center text-gray-700 dark:text-neutral-200 bg-transparent"
>
{document.name}
</td>
<td className="py-4 px-4 text-center text-sm text-gray-700 dark:text-[#E0E0E0] whitespace-nowrap w-[20%] group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50">
{document.date ? formatDate(document.date) : ''}
</td>
<td className="py-4 px-4 text-center text-sm text-gray-700 dark:text-[#E0E0E0] whitespace-nowrap w-[25%] group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50">
{document.tokens ? formatTokens(+document.tokens) : ''}
</td>
<td className="py-4 px-4 text-right w-[10%] group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50">
<div className="flex items-center justify-end gap-3">
{!document.syncFrequency && (
<div className="w-8"></div>
)}
{document.syncFrequency && (
<DropdownMenu
name={t('settings.documents.sync')}
options={syncOptions}
onSelect={(value: string) => {
handleManageSync(document, value);
}}
defaultValue={document.syncFrequency}
icon={SyncIcon}
/>
)}
<button
onClick={(event) => {
event.stopPropagation();
handleDeleteConfirmation(index, document);
}}
className="inline-flex items-center justify-center w-8 h-8 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors flex-shrink-0"
>
<img
src={Trash}
alt={t('convTile.delete')}
className="h-4 w-4 opacity-60 hover:opacity-100"
/>
</button>
</div>
{t('settings.documents.noData')}
</td>
</tr>
))
)}
</tbody>
</table>
) : (
currentDocuments.map((document, index) => (
<tr
key={index}
className="group transition-colors cursor-pointer"
onClick={() => setShowDocumentChunks(document)}
>
<td
className="py-4 px-4 text-sm text-gray-700 dark:text-[#E0E0E0] w-[45%] min-w-48 max-w-0 truncate group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50"
title={document.name}
>
{document.name}
</td>
<td className="py-4 px-4 text-center text-sm text-gray-700 dark:text-[#E0E0E0] whitespace-nowrap w-[20%] group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50">
{document.date ? formatDate(document.date) : ''}
</td>
<td className="py-4 px-4 text-center text-sm text-gray-700 dark:text-[#E0E0E0] whitespace-nowrap w-[25%] group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50">
{document.tokens
? formatTokens(+document.tokens)
: ''}
</td>
<td
className="py-4 px-4 text-right w-[10%] group-hover:bg-gray-50 dark:group-hover:bg-gray-800/50"
onClick={(e) => e.stopPropagation()} // Stop event propagation for the entire actions cell
>
<div className="flex items-center justify-end gap-3">
{!document.syncFrequency && (
<div className="w-8"></div>
)}
{document.syncFrequency && (
<DropdownMenu
name={t('settings.documents.sync')}
options={syncOptions}
onSelect={(value: string) => {
handleManageSync(document, value);
}}
defaultValue={document.syncFrequency}
icon={SyncIcon}
/>
)}
<button
onClick={() => {
handleDeleteConfirmation(index, document);
}}
className="inline-flex items-center justify-center w-8 h-8 rounded-full hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors flex-shrink-0"
>
<img
src={Trash}
alt={t('convTile.delete')}
className="h-4 w-4 opacity-60 hover:opacity-100"
/>
</button>
</div>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
</div>
</div>
</div>
@@ -532,11 +536,12 @@ function DocumentChunks({
</div>
) : (
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{paginatedChunks.filter((chunk) =>
chunk.metadata?.title
{paginatedChunks.filter((chunk) => {
if (!chunk.metadata?.title) return true;
return chunk.metadata.title
.toLowerCase()
.includes(searchTerm.toLowerCase()),
).length === 0 ? (
.includes(searchTerm.toLowerCase());
}).length === 0 ? (
<div className="mt-24 col-span-2 lg:col-span-3 text-center text-gray-500 dark:text-gray-400">
<img
src={isDarkTheme ? NoFilesDarkIcon : NoFilesIcon}
@@ -547,11 +552,12 @@ function DocumentChunks({
</div>
) : (
paginatedChunks
.filter((chunk) =>
chunk.metadata?.title
.filter((chunk) => {
if (!chunk.metadata?.title) return true;
return chunk.metadata.title
.toLowerCase()
.includes(searchTerm.toLowerCase()),
)
.includes(searchTerm.toLowerCase());
})
.map((chunk, index) => (
<div
key={index}
@@ -578,7 +584,7 @@ function DocumentChunks({
</div>
<div className="mt-[9px]">
<p className="h-12 text-sm font-semibold text-eerie-black dark:text-[#EEEEEE] leading-relaxed break-words ellipsis-text">
{chunk.metadata?.title}
{chunk.metadata?.title ?? 'Untitled'}
</p>
<p className="mt-1 pr-1 h-[110px] overflow-y-auto text-[13px] text-gray-600 dark:text-gray-400 leading-relaxed break-words">
{chunk.text}
@@ -591,11 +597,12 @@ function DocumentChunks({
</div>
)}
{!loading &&
paginatedChunks.filter((chunk) =>
chunk.metadata?.title
paginatedChunks.filter((chunk) => {
if (!chunk.metadata?.title) return true;
return chunk.metadata.title
.toLowerCase()
.includes(searchTerm.toLowerCase()),
).length !== 0 && (
.includes(searchTerm.toLowerCase());
}).length !== 0 && (
<div className="mt-10 w-full flex items-center justify-center">
<Pagination
currentPage={page}

View File

@@ -84,7 +84,7 @@ export default function General() {
return (
<div className="mt-12">
<div className="mb-5">
<label className="block font-bold text-jet dark:text-bright-gray">
<label className="block mb-2 font-bold text-jet dark:text-bright-gray">
{t('settings.general.selectTheme')}
</label>
<Dropdown

View File

@@ -3,11 +3,11 @@ import { useTranslation } from 'react-i18next';
import userService from '../api/services/userService';
import ChevronRight from '../assets/chevron-right.svg';
import CopyButton from '../components/CopyButton';
import Dropdown from '../components/Dropdown';
import SkeletonLoader from '../components/SkeletonLoader';
import { APIKeyData, LogData } from './types';
import CoppyButton from '../components/CopyButton';
import { useLoaderState } from '../hooks';
import { APIKeyData, LogData } from './types';
export default function Logs() {
const { t } = useTranslation();
@@ -148,7 +148,7 @@ function LogsTable({ logs, setPage, loading }: LogsTableProps) {
{logs?.map((log, index) => {
if (index === logs.length - 1) {
return (
<div ref={firstObserver} key={index}>
<div ref={firstObserver} key={index} className="w-full">
<Log log={log} />
</div>
);
@@ -170,26 +170,30 @@ function Log({ log }: { log: LogData }) {
const { id, action, timestamp, ...filteredLog } = log;
return (
<details className="group bg-transparent [&_summary::-webkit-details-marker]:hidden w-full hover:bg-[#F9F9F9] hover:dark:bg-dark-charcoal">
<summary className="flex flex-row items-center gap-2 text-gray-900 cursor-pointer p-2 group-open:bg-[#F9F9F9] dark:group-open:bg-dark-charcoal">
<summary className="flex flex-row items-start gap-2 text-gray-900 cursor-pointer p-2 group-open:bg-[#F9F9F9] dark:group-open:bg-dark-charcoal">
<img
src={ChevronRight}
alt="Expand log entry"
className="w-3 h-3 transition duration-300 group-open:rotate-90"
className="mt-[3px] w-3 h-3 transition duration-300 group-open:rotate-90"
/>
<span className="flex flex-row gap-2">
<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={`text-xs ${logLevelColor[log.level]}`}
>{`${log.question}`}</h2>
className={`max-w-72 text-xs ${logLevelColor[log.level]} break-words`}
>
{`${log.question}`.length > 250
? `${log.question.substring(0, 250)}...`
: log.question}
</h2>
</span>
</summary>
<div className="px-4 group-open:bg-[#F9F9F9] dark:group-open:bg-dark-charcoal">
<p className="px-2 leading-relaxed text-gray-700 dark:text-gray-400 text-xs">
<p className="px-2 leading-relaxed text-gray-700 dark:text-gray-400 text-xs break-words">
{JSON.stringify(filteredLog, null, 2)}
</p>
<div className="my-px w-8">
<CoppyButton
<CopyButton
text={JSON.stringify(filteredLog)}
colorLight="transparent"
/>

View File

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

View File

@@ -6,6 +6,7 @@ import CogwheelIcon from '../assets/cogwheel.svg';
import NoFilesDarkIcon from '../assets/no-files-dark.svg';
import NoFilesIcon from '../assets/no-files.svg';
import Input from '../components/Input';
import Spinner from '../components/Spinner';
import { useDarkTheme } from '../hooks';
import AddToolModal from '../modals/AddToolModal';
import { ActiveState } from '../models/misc';
@@ -22,8 +23,10 @@ export default function Tools() {
const [selectedTool, setSelectedTool] = React.useState<
UserToolType | APIToolType | null
>(null);
const [loading, setLoading] = React.useState(false);
const getUserTools = () => {
setLoading(true);
userService
.getUserTools()
.then((res) => {
@@ -31,6 +34,11 @@ export default function Tools() {
})
.then((data) => {
setUserTools(data.tools);
setLoading(false);
})
.catch((error) => {
console.error('Error fetching tools:', error);
setLoading(false);
});
};
@@ -78,7 +86,6 @@ export default function Tools() {
React.useEffect(() => {
getUserTools();
}, []);
return (
<div>
{selectedTool ? (
@@ -115,88 +122,99 @@ export default function Tools() {
{t('settings.tools.addTool')}
</button>
</div>
<div className="grid grid-cols-2 lg:grid-cols-3 gap-6">
{userTools.filter((tool) =>
tool.displayName
.toLowerCase()
.includes(searchTerm.toLowerCase()),
).length === 0 ? (
<div className="mt-24 col-span-2 lg:col-span-3 text-center text-gray-500 dark:text-gray-400">
<img
src={isDarkTheme ? NoFilesDarkIcon : NoFilesIcon}
alt="No tools found"
className="h-24 w-24 mx-auto mb-2"
/>
{t('settings.tools.noToolsFound')}
{loading ? (
<div className="grid grid-cols-2 lg:grid-cols-3 gap-6">
<div className="mt-24 h-32 col-span-2 lg:col-span-3 flex items-center justify-center">
<Spinner />
</div>
) : (
userTools
.filter((tool) =>
tool.displayName
.toLowerCase()
.includes(searchTerm.toLowerCase()),
)
.map((tool, index) => (
<div
key={index}
className="relative h-56 w-full p-6 border rounded-2xl border-silver dark:border-silver/40 flex flex-col justify-between"
>
<div className="w-full">
<div className="w-full flex items-center justify-between">
<img
src={`/toolIcons/tool_${tool.name}.svg`}
alt={`${tool.displayName} icon`}
className="h-8 w-8"
/>
<button
className="absolute top-3 right-3 cursor-pointer"
onClick={() => handleSettingsClick(tool)}
aria-label={t('settings.tools.configureToolAria', {
toolName: tool.displayName,
})}
>
</div>
) : (
<div className="grid grid-cols-2 lg:grid-cols-3 gap-6">
{userTools.filter((tool) =>
tool.displayName
.toLowerCase()
.includes(searchTerm.toLowerCase()),
).length === 0 ? (
<div className="mt-24 col-span-2 lg:col-span-3 text-center text-gray-500 dark:text-gray-400">
<img
src={isDarkTheme ? NoFilesDarkIcon : NoFilesIcon}
alt="No tools found"
className="h-24 w-24 mx-auto mb-2"
/>
{t('settings.tools.noToolsFound')}
</div>
) : (
userTools
.filter((tool) =>
tool.displayName
.toLowerCase()
.includes(searchTerm.toLowerCase()),
)
.map((tool, index) => (
<div
key={index}
className="relative h-56 w-full p-6 border rounded-2xl border-silver dark:border-silver/40 flex flex-col justify-between"
>
<div className="w-full">
<div className="w-full flex items-center justify-between">
<img
src={CogwheelIcon}
alt={t('settings.tools.settingsIconAlt')}
className="h-[19px] w-[19px]"
src={`/toolIcons/tool_${tool.name}.svg`}
alt={`${tool.displayName} icon`}
className="h-8 w-8"
/>
</button>
<button
className="absolute top-3 right-3 cursor-pointer"
onClick={() => handleSettingsClick(tool)}
aria-label={t(
'settings.tools.configureToolAria',
{
toolName: tool.displayName,
},
)}
>
<img
src={CogwheelIcon}
alt={t('settings.tools.settingsIconAlt')}
className="h-[19px] w-[19px]"
/>
</button>
</div>
<div className="mt-[9px]">
<p className="text-sm font-semibold text-eerie-black dark:text-[#EEEEEE] leading-relaxed">
{tool.displayName}
</p>
<p className="mt-1 h-16 overflow-auto text-[13px] text-gray-600 dark:text-gray-400 leading-relaxed pr-1">
{tool.description}
</p>
</div>
</div>
<div className="mt-[9px]">
<p className="text-sm font-semibold text-eerie-black dark:text-[#EEEEEE] leading-relaxed">
{tool.displayName}
</p>
<p className="mt-1 h-16 overflow-auto text-[13px] text-gray-600 dark:text-gray-400 leading-relaxed pr-1">
{tool.description}
</p>
<div className="absolute bottom-3 right-3">
<label
htmlFor={`toolToggle-${index}`}
className="relative inline-block h-6 w-10 cursor-pointer rounded-full bg-gray-300 dark:bg-[#D2D5DA33]/20 transition [-webkit-tap-highlight-color:_transparent] has-[:checked]:bg-[#0C9D35CC] has-[:checked]:dark:bg-[#0C9D35CC]"
>
<span className="sr-only">
{t('settings.tools.toggleToolAria', {
toolName: tool.displayName,
})}
</span>
<input
type="checkbox"
id={`toolToggle-${index}`}
className="peer sr-only"
checked={tool.status}
onChange={() =>
updateToolStatus(tool.id, !tool.status)
}
/>
<span className="absolute inset-y-0 start-0 m-[3px] size-[18px] rounded-full bg-white transition-all peer-checked:start-4"></span>
</label>
</div>
</div>
<div className="absolute bottom-3 right-3">
<label
htmlFor={`toolToggle-${index}`}
className="relative inline-block h-6 w-10 cursor-pointer rounded-full bg-gray-300 dark:bg-[#D2D5DA33]/20 transition [-webkit-tap-highlight-color:_transparent] has-[:checked]:bg-[#0C9D35CC] has-[:checked]:dark:bg-[#0C9D35CC]"
>
<span className="sr-only">
{t('settings.tools.toggleToolAria', {
toolName: tool.displayName,
})}
</span>
<input
type="checkbox"
id={`toolToggle-${index}`}
className="peer sr-only"
checked={tool.status}
onChange={() =>
updateToolStatus(tool.id, !tool.status)
}
/>
<span className="absolute inset-y-0 start-0 m-[3px] size-[18px] rounded-full bg-white transition-all peer-checked:start-4"></span>
</label>
</div>
</div>
))
)}
</div>
))
)}
</div>
)}
</div>
<AddToolModal
message={t('settings.tools.selectToolSetup')}

View File

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