diff --git a/application/llm/openai.py b/application/llm/openai.py index 36861584..b8f311b0 100644 --- a/application/llm/openai.py +++ b/application/llm/openai.py @@ -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, diff --git a/application/requirements.txt b/application/requirements.txt index 1707ad80..ffb96ecd 100644 --- a/application/requirements.txt +++ b/application/requirements.txt @@ -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 diff --git a/frontend/src/Hero.tsx b/frontend/src/Hero.tsx index 9fe965a1..a000ab2e 100644 --- a/frontend/src/Hero.tsx +++ b/frontend/src/Hero.tsx @@ -37,7 +37,7 @@ export default function Hero({ -
-

- New Action -

-
- - Action Name - - setActionName(e.target.value)} - borderVariant="thin" - placeholder={'Enter name'} - /> -

- Use only letters, numbers, underscores, and hyphens (e.g., - `get_user_data`, `send-report`). -

- {functionNameError && ( -

- Invalid function name format. Use only letters, numbers, - underscores, and hyphens. -

- )} -
-
- - -
-
+ {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.)'} +

- - +
+ + +
+ + ); } diff --git a/frontend/src/modals/AddToolModal.tsx b/frontend/src/modals/AddToolModal.tsx index ba555df4..07360eb7 100644 --- a/frontend/src/modals/AddToolModal.tsx +++ b/frontend/src/modals/AddToolModal.tsx @@ -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(null); const [availableTools, setAvailableTools] = React.useState< AvailableToolType[] >([]); @@ -28,8 +31,7 @@ export default function AddToolModal({ React.useState(null); const [configModalState, setConfigModalState] = React.useState('INACTIVE'); - const modalRef = useRef(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 ( <> -
-
setModalState('INACTIVE')} + className="h-[85vh] w-[90vw] md:w-[75vw]" > -
- -
+
+

{t('settings.tools.selectToolSetup')}

- {availableTools.map((tool, index) => ( -
{ - setSelectedTool(tool); - handleAddTool(tool); - }} - onKeyDown={(e) => { - if (e.key === 'Enter' || e.key === ' ') { + {loading ? ( +
+ +
+ ) : ( + availableTools.map((tool, index) => ( +
{ setSelectedTool(tool); handleAddTool(tool); - } - }} - > -
-
- -
-
-

- {tool.displayName} -

-

- {tool.description} -

+ }} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === ' ') { + setSelectedTool(tool); + handleAddTool(tool); + } + }} + > +
+
+ +
+
+

+ {tool.displayName} +

+

+ {tool.description} +

+
-
- ))} + )) + )}
-
-
+ + )} 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(''); @@ -36,63 +38,47 @@ export default function ConfigToolModal({ getUserTools(); }); }; + + // Only render when modal is active + if (modalState !== 'ACTIVE') return null; + return ( -
-
-
- -
-

- {t('modals.configTool.title')} -

-

- {t('modals.configTool.type')}:{' '} - {tool?.name} -

-
- - {t('modals.configTool.apiKeyLabel')} - - setAuthKey(e.target.value)} - borderVariant="thin" - placeholder={t('modals.configTool.apiKeyPlaceholder')} - > -
-
- - -
-
+ setModalState('INACTIVE')}> +
+

+ {t('modals.configTool.title')} +

+

+ {t('modals.configTool.type')}:{' '} + {tool?.name} +

+
+ setAuthKey(e.target.value)} + borderVariant="thin" + placeholder={t('modals.configTool.apiKeyPlaceholder')} + label={t('modals.configTool.apiKeyLabel')} + />
-
-
+
+ + +
+ + ); } diff --git a/frontend/src/modals/ConfirmationModal.tsx b/frontend/src/modals/ConfirmationModal.tsx index 1a2e22e2..28b9c582 100644 --- a/frontend/src/modals/ConfirmationModal.tsx +++ b/frontend/src/modals/ConfirmationModal.tsx @@ -32,7 +32,7 @@ export default function ConfirmationModal({ >
-

+

{message}

diff --git a/frontend/src/modals/CreateAPIKeyModal.tsx b/frontend/src/modals/CreateAPIKeyModal.tsx index 5c8c75b8..128bdff6 100644 --- a/frontend/src/modals/CreateAPIKeyModal.tsx +++ b/frontend/src/modals/CreateAPIKeyModal.tsx @@ -73,21 +73,20 @@ export default function CreateAPIKeyModal({ handleFetchPrompts(); }, []); return ( - +
{t('modals.createAPIKey.label')}
- - {t('modals.createAPIKey.apiKeyName')} - setAPIKeyName(e.target.value)} + borderVariant="thin" >
diff --git a/frontend/src/modals/SaveAPIKeyModal.tsx b/frontend/src/modals/SaveAPIKeyModal.tsx index d91d0c2d..abe862cb 100644 --- a/frontend/src/modals/SaveAPIKeyModal.tsx +++ b/frontend/src/modals/SaveAPIKeyModal.tsx @@ -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 ( -
-
- -

- {' '} - {t('modals.saveKey.note')} -

-

- {t('modals.saveKey.disclaimer')} -

-
-
-

API Key

- {apiKey} -
- + +

+ {t('modals.saveKey.note')} +

+

+ {t('modals.saveKey.disclaimer')} +

+
+
+

+ API Key +

+ + {apiKey} +
-
+ + ); } diff --git a/frontend/src/modals/WrapperModal.tsx b/frontend/src/modals/WrapperModal.tsx index dcf1bebb..6d5d8543 100644 --- a/frontend/src/modals/WrapperModal.tsx +++ b/frontend/src/modals/WrapperModal.tsx @@ -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(null); @@ -40,17 +49,17 @@ export default function WrapperModal({
{!isPerformingTask && ( )} - {children} +
{children}
); diff --git a/frontend/src/modals/index.tsx b/frontend/src/modals/index.tsx deleted file mode 100644 index 1dcffd6d..00000000 --- a/frontend/src/modals/index.tsx +++ /dev/null @@ -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 ( -
- {props.render()} -
-
- - {props.isCancellable && ( - - )} -
- {props.isError && ( -

- {props.errorMessage} -

- )} -
-
- ); -}; - -export default Modal; diff --git a/frontend/src/preferences/APIKeyModal.tsx b/frontend/src/preferences/APIKeyModal.tsx deleted file mode 100644 index 43698fe1..00000000 --- a/frontend/src/preferences/APIKeyModal.tsx +++ /dev/null @@ -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 ( - { - return ( -
-

OpenAI API Key

-

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

- setKey(e.target.value)} - > -
- ); - }} - /> - ); -} diff --git a/frontend/src/preferences/PromptsModal.tsx b/frontend/src/preferences/PromptsModal.tsx index 11cb0685..f2512210 100644 --- a/frontend/src/preferences/PromptsModal.tsx +++ b/frontend/src/preferences/PromptsModal.tsx @@ -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 ( -
- -
-

- {t('modals.prompts.addPrompt')} -

-

- {t('modals.prompts.addDescription')} -

-
- - setNewPromptName(e.target.value)} - /> -
- - {t('modals.prompts.promptName')} - -
-
- - {t('modals.prompts.promptText')} - -
- - -
-
- +
+

+ {t('modals.prompts.addPrompt')} +

+

+ {t('modals.prompts.addDescription')} +

+
+ + setNewPromptName(e.target.value)} + /> +
+ + {t('modals.prompts.promptText')} +
+ + +
+
+
); @@ -114,16 +97,7 @@ function EditPrompt({ const { t } = useTranslation(); return ( -
- +

{t('modals.prompts.editPrompt')} @@ -271,15 +245,19 @@ export default function PromptsModal({ } else { view = <>; } - return ( -

{ + setModalState('INACTIVE'); + if (type === 'ADD') { + setNewPromptName(''); + setNewPromptContent(''); + } + }} + className="sm:w-[512px] mt-24" > -
- {view} -
-
- ); + {view} + + ) : null; } diff --git a/frontend/src/settings/APIKeys.tsx b/frontend/src/settings/APIKeys.tsx index 642566f1..6d76d7e8 100644 --- a/frontend/src/settings/APIKeys.tsx +++ b/frontend/src/settings/APIKeys.tsx @@ -98,111 +98,132 @@ export default function APIKeys() { }, []); return ( -
-
-
+
+
+
+

+ {t('settings.apiKeys.description')} +

+
+ +
- {isCreateModalOpen && ( - setCreateModal(false)} - /> - )} - {isSaveKeyModalOpen && ( - setSaveKeyModal(false)} - /> - )} - {keyToDelete && ( - setKeyToDelete(null)} - submitLabel={t('modals.deleteConv.delete')} - handleSubmit={() => handleDeleteKey(keyToDelete.id)} - handleCancel={() => setKeyToDelete(null)} - /> - )} -
-
-
-
-
- - - - - - - - - - - {loading ? ( - - ) : !apiKeys?.length ? ( - - - - ) : ( - Array.isArray(apiKeys) && - apiKeys.map((element, index) => ( - - - - - + + )) + )} + +
- {t('settings.apiKeys.name')} - - {t('settings.apiKeys.sourceDoc')} - - {t('settings.apiKeys.key')} -
- {t('settings.apiKeys.noData')} -
{element.name}{element.source}{element.key} + +
+
+
+ + + + + + + + + + + {loading ? ( + + ) : !apiKeys?.length ? ( + + + + ) : ( + Array.isArray(apiKeys) && + apiKeys.map((element) => ( + + + + + - - )) - )} - -
+ {t('settings.apiKeys.name')} + + {t('settings.apiKeys.sourceDoc')} + + + {t('settings.apiKeys.key')} + + + {t('settings.apiKeys.key')} + + + Actions +
+ {t('settings.apiKeys.noData')} +
+
+ {element.name} +
+
+
+ {element.source} +
+
+
+ {element.key} +
+
+
+
-
-
+ +
+
+ {isCreateModalOpen && ( + setCreateModal(false)} + /> + )} + {isSaveKeyModalOpen && ( + setSaveKeyModal(false)} /> + )} + {keyToDelete && ( + setKeyToDelete(null)} + submitLabel={t('modals.deleteConv.delete')} + handleSubmit={() => handleDeleteKey(keyToDelete.id)} + handleCancel={() => setKeyToDelete(null)} + /> + )}
); } diff --git a/frontend/src/settings/Analytics.tsx b/frontend/src/settings/Analytics.tsx index 4d390495..53538833 100644 --- a/frontend/src/settings/Analytics.tsx +++ b/frontend/src/settings/Analytics.tsx @@ -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 (
-
-

- {t('settings.analytics.filterByChatbot')} -

- ({ - 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" - /> -
+ {loadingChatbots ? ( + + ) : ( +
+

+ {t('settings.analytics.filterByChatbot')} +

+ ({ + 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" + /> +
+ )} {/* Messages Analytics */}
diff --git a/frontend/src/settings/Documents.tsx b/frontend/src/settings/Documents.tsx index e72a6e16..1203758d 100644 --- a/frontend/src/settings/Documents.tsx +++ b/frontend/src/settings/Documents.tsx @@ -176,14 +176,14 @@ export default function Documents({ }} /> ) : ( -
+

{t('settings.documents.title')}

-
+
- -
- {' '} +
- - - - - - setShowDocumentChunks(document)} + > + + + + + + )) + )} + +
- {t('settings.documents.name')} - -
- {t('settings.documents.date')} - refreshDocs('date')} - src={caretSort} - alt="sort" - /> -
-
-
- - {t('settings.documents.tokenUsage')} +
+ + + + + + + - - - - - {loading ? ( - - ) : !currentDocuments?.length ? ( - - + - ) : ( - currentDocuments.map((document, index) => ( - setShowDocumentChunks(document)} - > + + + {loading ? ( + + ) : !currentDocuments?.length ? ( + - - - - )) - )} - -
+ {t('settings.documents.name')} + +
+ {t('settings.documents.date')} + refreshDocs('date')} + src={caretSort} + alt="sort" + /> +
+
+
+ + {t('settings.documents.tokenUsage')} + + + {t('settings.documents.tokenUsage')} + + refreshDocs('tokens')} + src={caretSort} + alt="sort" + /> +
+
+ + {t('settings.documents.actions')} - - {t('settings.documents.tokenUsage')} - - refreshDocs('tokens')} - src={caretSort} - alt="sort" - /> - - - - {t('settings.documents.actions')} - -
- {t('settings.documents.noData')} -
- {document.name} - - {document.date ? formatDate(document.date) : ''} - - {document.tokens ? formatTokens(+document.tokens) : ''} - -
- {!document.syncFrequency && ( -
- )} - {document.syncFrequency && ( - { - handleManageSync(document, value); - }} - defaultValue={document.syncFrequency} - icon={SyncIcon} - /> - )} - -
+ {t('settings.documents.noData')}
+ ) : ( + currentDocuments.map((document, index) => ( +
+ {document.name} + + {document.date ? formatDate(document.date) : ''} + + {document.tokens + ? formatTokens(+document.tokens) + : ''} + e.stopPropagation()} // Stop event propagation for the entire actions cell + > +
+ {!document.syncFrequency && ( +
+ )} + {document.syncFrequency && ( + { + handleManageSync(document, value); + }} + defaultValue={document.syncFrequency} + icon={SyncIcon} + /> + )} + +
+
+
@@ -532,11 +536,12 @@ function DocumentChunks({
) : (
- {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 ? (
) : ( 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) => (

- {chunk.metadata?.title} + {chunk.metadata?.title ?? 'Untitled'}

{chunk.text} @@ -591,11 +597,12 @@ function DocumentChunks({

)} {!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 && (
-