Merge pull request #1628 from ManishMadan2882/main

Smoother transition in settings
This commit is contained in:
Alex
2025-02-14 14:47:16 +00:00
committed by GitHub
9 changed files with 423 additions and 322 deletions

View File

@@ -18,6 +18,7 @@ module.exports = {
},
plugins: ['react', 'unused-imports'],
rules: {
'react/prop-types': 'off',
'unused-imports/no-unused-imports': 'error',
'react/react-in-jsx-scope': 'off',
'prettier/prettier': [

View File

@@ -1,8 +1,14 @@
import React, { useState, useEffect } from 'react';
import { useState, useEffect } from 'react';
interface SkeletonLoaderProps {
count?: number;
component?: 'default' | 'analysis' | 'chatbot' | 'logs';
component?:
| 'default'
| 'analysis'
| 'logs'
| 'table'
| 'chatbot'
| 'dropdown';
}
const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
@@ -32,107 +38,162 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
};
}, [count]);
return (
<div className="flex flex-col space-y-4">
{component === 'default' ? (
[...Array(skeletonCount)].map((_, idx) => (
<div
key={idx}
className={`p-6 ${skeletonCount === 1 ? 'w-full' : 'w-60'} dark:bg-raisin-black rounded-3xl animate-pulse`}
>
<div className="space-y-4">
<div>
<div className="h-4 bg-gray-600 rounded mb-2 w-3/4"></div>
<div className="h-4 bg-gray-600 rounded mb-2 w-5/6"></div>
<div className="h-4 bg-gray-600 rounded mb-2 w-1/2"></div>
<div className="h-4 bg-gray-600 rounded mb-2 w-3/4"></div>
<div className="h-4 bg-gray-600 rounded mb-2 w-full"></div>
</div>
<div className="border-t border-gray-600 my-4"></div>
<div>
<div className="h-4 bg-gray-600 rounded mb-2 w-2/3"></div>
<div className="h-4 bg-gray-600 rounded mb-2 w-1/4"></div>
<div className="h-4 bg-gray-600 rounded mb-2 w-full"></div>
</div>
<div className="border-t border-gray-600 my-4"></div>
<div>
<div className="h-4 bg-gray-600 rounded mb-2 w-5/6"></div>
<div className="h-4 bg-gray-600 rounded mb-2 w-1/3"></div>
<div className="h-4 bg-gray-600 rounded mb-2 w-2/3"></div>
<div className="h-4 bg-gray-600 rounded mb-2 w-full"></div>
</div>
<div className="border-t border-gray-600 my-4"></div>
<div className="h-4 bg-gray-600 rounded w-3/4 mb-2"></div>
<div className="h-4 bg-gray-600 rounded w-5/6 mb-2"></div>
</div>
</div>
))
) : component === 'analysis' ? (
[...Array(skeletonCount)].map((_, idx) => (
<div
key={idx}
className="p-6 w-full dark:bg-raisin-black rounded-3xl animate-pulse"
>
<div className="space-y-6">
<div className="space-y-2">
<div className="h-4 bg-gray-600 rounded w-1/3 mb-4"></div>
<div className="grid grid-cols-6 gap-2 items-end">
<div className="h-32 bg-gray-600 rounded"></div>
<div className="h-24 bg-gray-600 rounded"></div>
<div className="h-40 bg-gray-600 rounded"></div>
<div className="h-28 bg-gray-600 rounded"></div>
<div className="h-36 bg-gray-600 rounded"></div>
<div className="h-20 bg-gray-600 rounded"></div>
</div>
</div>
<div className="space-y-2">
<div className="h-4 bg-gray-600 rounded w-1/4 mb-4"></div>
<div className="h-32 bg-gray-600 rounded"></div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="h-4 bg-gray-600 rounded w-full"></div>
<div className="h-4 bg-gray-600 rounded w-full"></div>
</div>
</div>
</div>
))
) : component === 'chatbot' ? (
<div className="space-y-2 p-6 w-full dark:bg-raisin-black rounded-3xl animate-pulse">
<div className="grid grid-cols-4 gap-2 p-2">
<div className="h-4 bg-gray-600 rounded w-full"></div>
<div className="h-4 bg-gray-600 rounded w-full"></div>
<div className="h-4 bg-gray-600 rounded w-full"></div>
<div className="h-4 bg-gray-600 rounded w-full"></div>
</div>
<div className="border-t border-gray-600 my-2"></div>
const renderTable = () => (
<>
{[...Array(4)].map((_, idx) => (
<tr key={idx} className="animate-pulse">
<td className="py-4 px-4 w-[45%]">
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-full"></div>
</td>
<td className="py-4 px-4 w-[20%]">
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-full"></div>
</td>
<td className="py-4 px-4 w-[25%]">
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-full"></div>
</td>
<td className="py-4 px-4 w-[10%]">
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-full"></div>
</td>
</tr>
))}
</>
);
{[...Array(skeletonCount * 6)].map((_, idx) => (
<div key={idx} className="grid grid-cols-4 gap-2 p-2 space-x-2">
<div className="h-4 bg-gray-500 rounded w-full"></div>
<div className="h-4 bg-gray-500 rounded w-full"></div>
<div className="h-4 bg-gray-500 rounded w-full"></div>
<div className="h-4 bg-gray-500 rounded w-full"></div>
</div>
))}
</div>
) : (
[...Array(skeletonCount)].map((_, idx) => (
<div
key={idx}
className="p-6 w-full dark:bg-raisin-black rounded-3xl animate-pulse"
>
<div className="space-y-4">
<div className="h-4 bg-gray-600 rounded w-1/2"></div>
<div className="h-4 bg-gray-600 rounded w-5/6"></div>
<div className="h-4 bg-gray-600 rounded w-3/4"></div>
<div className="h-4 bg-gray-600 rounded w-2/3"></div>
<div className="h-4 bg-gray-600 rounded w-1/4"></div>
</div>
</div>
))
)}
const renderChatbot = () => (
<>
{[...Array(4)].map((_, idx) => (
<tr key={idx} className="animate-pulse">
<td className="p-2">
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-3/4 mx-auto"></div>
</td>
<td className="p-2">
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-full mx-auto"></div>
</td>
<td className="p-2">
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-full mx-auto"></div>
</td>
<td className="p-2">
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-8 mx-auto"></div>
</td>
</tr>
))}
</>
);
const renderDropdown = () => (
<div className="animate-pulse">
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-24 mb-2"></div>
<div className="w-[360px] h-14 bg-gray-300 dark:bg-gray-600 rounded-3xl flex items-center justify-between px-4">
<div className="h-3 bg-gray-400 dark:bg-gray-700 rounded w-24"></div>
<div className="h-3 w-3 bg-gray-400 dark:bg-gray-700 rounded"></div>
</div>
</div>
);
const renderLogs = () => (
<div className="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"
>
<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>
</div>
</div>
))}
</div>
);
const renderDefault = () => (
<>
{[...Array(skeletonCount)].map((_, idx) => (
<div
key={idx}
className={`p-6 ${
skeletonCount === 1 ? 'w-full' : 'w-60'
} dark:bg-raisin-black rounded-3xl animate-pulse`}
>
<div className="space-y-4">
<div>
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded mb-2 w-3/4"></div>
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded mb-2 w-5/6"></div>
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded mb-2 w-1/2"></div>
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded mb-2 w-3/4"></div>
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded mb-2 w-full"></div>
</div>
<div className="border-t border-gray-400 dark:border-gray-700 my-4"></div>
<div>
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded mb-2 w-2/3"></div>
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded mb-2 w-1/4"></div>
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded mb-2 w-full"></div>
</div>
<div className="border-t border-gray-400 dark:border-gray-700 my-4"></div>
<div>
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded mb-2 w-5/6"></div>
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded mb-2 w-1/3"></div>
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded mb-2 w-2/3"></div>
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded mb-2 w-full"></div>
</div>
<div className="border-t border-gray-400 dark:border-gray-700 my-4"></div>
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-3/4 mb-2"></div>
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-5/6 mb-2"></div>
</div>
</div>
))}
</>
);
const renderAnalysis = () => (
<>
{[...Array(skeletonCount)].map((_, idx) => (
<div
key={idx}
className="p-6 w-full dark:bg-raisin-black rounded-3xl animate-pulse"
>
<div className="space-y-6">
<div className="space-y-2">
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-1/3 mb-4"></div>
<div className="grid grid-cols-6 gap-2 items-end">
<div className="h-32 bg-gray-300 dark:bg-gray-600 rounded"></div>
<div className="h-24 bg-gray-300 dark:bg-gray-600 rounded"></div>
<div className="h-40 bg-gray-300 dark:bg-gray-600 rounded"></div>
<div className="h-28 bg-gray-300 dark:bg-gray-600 rounded"></div>
<div className="h-36 bg-gray-300 dark:bg-gray-600 rounded"></div>
<div className="h-20 bg-gray-300 dark:bg-gray-600 rounded"></div>
</div>
</div>
<div className="space-y-2">
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-1/4 mb-4"></div>
<div className="h-32 bg-gray-300 dark:bg-gray-600 rounded"></div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-full"></div>
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-full"></div>
</div>
</div>
</div>
))}
</>
);
const componentMap = {
table: renderTable,
chatbot: renderChatbot,
dropdown: renderDropdown,
logs: renderLogs,
default: renderDefault,
analysis: renderAnalysis,
};
const render = componentMap[component] || componentMap.default;
return <>{render()}</>;
};
export default SkeletonLoader;

View File

@@ -359,7 +359,7 @@ const ConversationBubble = forwardRef<
</SyntaxHighlighter>
<div
className={`absolute right-3 top-3 lg:invisible
${type !== 'ERROR' ? 'group-hover:lg:visible' : ''} `}
${type !== 'ERROR' ? 'group-hover:lg:visible' : ''} `}
>
<CopyButton
text={String(children).replace(/\n$/, '')}
@@ -367,7 +367,9 @@ const ConversationBubble = forwardRef<
</div>
</div>
) : (
<code className="whitespace-pre-line">{children}</code>
<code className="whitespace-pre-line rounded-[6px] bg-gray-200 px-[8px] py-[4px] text-xs font-normal dark:bg-independence dark:text-bright-gray">
{children}
</code>
);
},
ul({ children }) {
@@ -390,8 +392,8 @@ const ConversationBubble = forwardRef<
},
table({ children }) {
return (
<div className="relative overflow-x-auto rounded-lg border">
<table className="w-full text-left text-sm text-gray-700">
<div className="relative overflow-x-auto rounded-lg border border-silver/40 dark:border-silver/40">
<table className="w-full text-left text-gray-700 dark:text-bright-gray">
{children}
</table>
</div>
@@ -399,24 +401,24 @@ const ConversationBubble = forwardRef<
},
thead({ children }) {
return (
<thead className="text-xs uppercase text-gray-900 [&>.table-row]:bg-gray-50">
<thead className="text-xs uppercase text-gray-900 dark:text-bright-gray bg-gray-50 dark:bg-[#26272E]/50">
{children}
</thead>
);
},
tr({ children }) {
return (
<tr className="table-row border-b odd:bg-white even:bg-gray-50">
<tr className="border-b border-gray-200 dark:border-silver/40 odd:bg-white dark:odd:bg-[#26272E] even:bg-gray-50 dark:even:bg-[#26272E]/50">
{children}
</tr>
);
},
td({ children }) {
return <td className="px-6 py-3">{children}</td>;
},
th({ children }) {
return <th className="px-6 py-3">{children}</th>;
},
td({ children }) {
return <td className="px-6 py-3">{children}</td>;
},
}}
>
{preprocessLaTeX(message)}

View File

@@ -112,3 +112,23 @@ export function useDarkTheme() {
return [isDarkTheme, toggleTheme, componentMounted] as const;
}
export function useLoaderState(
initialState = false,
delay = 250,
): [boolean, (value: boolean) => void] {
const [state, setState] = useState<boolean>(initialState);
const setLoaderState = (value: boolean) => {
if (value) {
setState(true);
} else {
// Only add delay when changing from true to false
setTimeout(() => {
setState(false);
}, delay);
}
};
return [state, setLoaderState];
}

View File

@@ -8,14 +8,15 @@ import SaveAPIKeyModal from '../modals/SaveAPIKeyModal';
import ConfirmationModal from '../modals/ConfirmationModal';
import { APIKeyData } from './types';
import SkeletonLoader from '../components/SkeletonLoader';
import { useLoaderState } from '../hooks';
export default function APIKeys() {
const { t } = useTranslation();
const [isCreateModalOpen, setCreateModal] = React.useState(false);
const [isSaveKeyModalOpen, setSaveKeyModal] = React.useState(false);
const [newKey, setNewKey] = React.useState('');
const [apiKeys, setApiKeys] = React.useState<APIKeyData[]>([]);
const [loading, setLoading] = useState(true);
const [isCreateModalOpen, setCreateModal] = useState(false);
const [isSaveKeyModalOpen, setSaveKeyModal] = useState(false);
const [newKey, setNewKey] = useState('');
const [apiKeys, setApiKeys] = useState<APIKeyData[]>([]);
const [loading, setLoading] = useLoaderState(true);
const [keyToDelete, setKeyToDelete] = useState<{
id: string;
name: string;
@@ -38,6 +39,7 @@ export default function APIKeys() {
};
const handleDeleteKey = (id: string) => {
setLoading(true);
userService
.deleteAPIKey({ id })
.then((response) => {
@@ -47,12 +49,16 @@ export default function APIKeys() {
return response.json();
})
.then((data) => {
data.success === true &&
if (data.success === true) {
setApiKeys((previous) => previous.filter((elem) => elem.id !== id));
}
setKeyToDelete(null);
})
.catch((error) => {
console.error(error);
})
.finally(() => {
setLoading(false);
});
};
@@ -63,6 +69,7 @@ export default function APIKeys() {
prompt_id: string;
chunks: string;
}) => {
setLoading(true);
userService
.createAPIKey(payload)
.then((response) => {
@@ -80,6 +87,9 @@ export default function APIKeys() {
})
.catch((error) => {
console.error(error);
})
.finally(() => {
setLoading(false);
});
};
@@ -124,73 +134,72 @@ export default function APIKeys() {
)}
<div className="mt-[27px] w-full">
<div className="w-full overflow-x-auto">
{loading ? (
<SkeletonLoader count={1} component={'chatbot'} />
) : (
<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>
<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>
</thead>
<tbody className="divide-y divide-gray-200 dark:divide-neutral-700">
{!apiKeys?.length && (
<tr>
<td
colSpan={4}
className="!p-4 text-gray-800 dark:text-neutral-200 text-center"
>
{t('settings.apiKeys.noData')}
) : (
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">
<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,
})
}
/>
</td>
</tr>
)}
{Array.isArray(apiKeys) &&
apiKeys?.map((element, index) => (
<tr
key={index}
className="text-nowrap whitespace-nowrap text-center text-sm font-medium text-gray-800 dark:text-neutral-200 p-2"
>
<td className="p-1">{element.name}</td>
<td className="p-2">{element.source}</td>
<td>{element.key}</td>
<td>
<img
src={Trash}
alt={`Delete ${element.name}`}
className="h-4 w-4 cursor-pointer hover:opacity-50"
id={`img-${index}`}
onClick={() =>
setKeyToDelete({
id: element.id,
name: element.name,
})
}
/>
</td>
</tr>
))}
</tbody>
</table>
</div>
))
)}
</tbody>
</table>
</div>
</div>
)}
</div>
</div>
</div>
</div>

View File

@@ -16,6 +16,7 @@ import Dropdown from '../components/Dropdown';
import { htmlLegendPlugin } from '../utils/chartUtils';
import { formatDate } from '../utils/dateTimeUtils';
import { APIKeyData } from './types';
import { useLoaderState } from '../hooks';
import type { ChartData } from 'chart.js';
import SkeletonLoader from '../components/SkeletonLoader';
@@ -88,9 +89,9 @@ export default function Analytics() {
value: 'last_30_days',
});
const [loadingMessages, setLoadingMessages] = useState(true);
const [loadingTokens, setLoadingTokens] = useState(true);
const [loadingFeedback, setLoadingFeedback] = useState(true);
const [loadingMessages, setLoadingMessages] = useLoaderState(true);
const [loadingTokens, setLoadingTokens] = useLoaderState(true);
const [loadingFeedback, setLoadingFeedback] = useLoaderState(true);
const fetchChatbots = async () => {
try {

View File

@@ -15,7 +15,7 @@ import DropdownMenu from '../components/DropdownMenu';
import Input from '../components/Input';
import SkeletonLoader from '../components/SkeletonLoader';
import Spinner from '../components/Spinner';
import { useDarkTheme } from '../hooks';
import { useDarkTheme, useLoaderState } from '../hooks';
import ChunkModal from '../modals/ChunkModal';
import ConfirmationModal from '../modals/ConfirmationModal';
import { ActiveState, Doc, DocumentsProps } from '../models/misc';
@@ -54,7 +54,7 @@ export default function Documents({
const [searchTerm, setSearchTerm] = useState<string>('');
const [modalState, setModalState] = useState<ActiveState>('INACTIVE');
const [isOnboarding, setIsOnboarding] = useState<boolean>(false);
const [loading, setLoading] = useState<boolean>(false);
const [loading, setLoading] = useLoaderState(false);
const [sortField, setSortField] = useState<'date' | 'tokens'>('date');
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
// Pagination
@@ -214,123 +214,118 @@ export default function Documents({
</button>
</div>
{loading ? (
<SkeletonLoader count={1} />
) : (
<div className="flex flex-col flex-grow">
{' '}
{/* Removed overflow-auto */}
<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')}
</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')}
<div className="flex flex-col flex-grow">
{' '}
<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')}
</span>
</th>
<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>
</tr>
</thead>
<tbody className="divide-y divide-gray-300 dark:divide-silver/40">
{!currentDocuments?.length ? (
<tr>
) : (
currentDocuments.map((document, index) => (
<tr
key={index}
className="group transition-colors"
onClick={() => setShowDocumentChunks(document)}
>
<td
colSpan={4}
className="py-4 text-center text-gray-700 dark:text-[#E0E0E] bg-transparent"
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}
>
{t('settings.documents.noData')}
{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>
</td>
</tr>
) : (
currentDocuments.map((document, index) => (
<tr
key={index}
className="group transition-colors"
onClick={() => setShowDocumentChunks(document)}
>
<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}
>
{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>
</td>
</tr>
))
)}
</tbody>
</table>
</div>
))
)}
</tbody>
</table>
</div>
)}
</div>
</div>
<div className="mt-auto pt-4">
@@ -395,7 +390,7 @@ function DocumentChunks({
const [page, setPage] = useState(1);
const [perPage, setPerPage] = useState(5);
const [totalChunks, setTotalChunks] = useState(0);
const [loading, setLoading] = useState(true);
const [loading, setLoading] = useLoaderState(true);
const [searchTerm, setSearchTerm] = useState<string>('');
const [addModal, setAddModal] = useState<ActiveState>('INACTIVE');
const [editModal, setEditModal] = useState<{
@@ -425,6 +420,7 @@ function DocumentChunks({
});
} catch (e) {
console.log(e);
setLoading(false);
}
};

View File

@@ -7,6 +7,7 @@ import Dropdown from '../components/Dropdown';
import SkeletonLoader from '../components/SkeletonLoader';
import { APIKeyData, LogData } from './types';
import CoppyButton from '../components/CopyButton';
import { useLoaderState } from '../hooks';
export default function Logs() {
const { t } = useTranslation();
@@ -15,8 +16,8 @@ export default function Logs() {
const [logs, setLogs] = useState<LogData[]>([]);
const [page, setPage] = useState(1);
const [hasMore, setHasMore] = useState(true);
const [loadingChatbots, setLoadingChatbots] = useState(true);
const [loadingLogs, setLoadingLogs] = useState(true);
const [loadingChatbots, setLoadingChatbots] = useLoaderState(true);
const [loadingLogs, setLoadingLogs] = useLoaderState(true);
const fetchChatbots = async () => {
setLoadingChatbots(true);
@@ -66,16 +67,16 @@ export default function Logs() {
return (
<div className="mt-12">
<div className="flex flex-col items-start">
<div className="flex flex-col gap-3">
<label
id="chatbot-filter-label"
className="font-bold text-jet dark:text-bright-gray"
>
{t('settings.logs.filterByChatbot')}
</label>
{loadingChatbots ? (
<SkeletonLoader />
) : (
{loadingChatbots ? (
<SkeletonLoader component="dropdown" />
) : (
<div className="flex flex-col gap-3">
<label
id="chatbot-filter-label"
className="font-bold text-jet dark:text-bright-gray"
>
{t('settings.logs.filterByChatbot')}
</label>
<Dropdown
size="w-[55vw] sm:w-[360px]"
options={[
@@ -104,16 +105,12 @@ export default function Logs() {
rounded="3xl"
border="border"
/>
)}
</div>
</div>
)}
</div>
<div className="mt-8">
{loadingLogs ? (
<SkeletonLoader component={'logs'} />
) : (
<LogsTable logs={logs} setPage={setPage} />
)}
<LogsTable logs={logs} setPage={setPage} loading={loadingLogs} />
</div>
</div>
);
@@ -122,9 +119,10 @@ export default function Logs() {
type LogsTableProps = {
logs: LogData[];
setPage: React.Dispatch<React.SetStateAction<number>>;
loading: boolean;
};
function LogsTable({ logs, setPage }: LogsTableProps) {
function LogsTable({ logs, setPage, loading }: LogsTableProps) {
const { t } = useTranslation();
const observerRef = useRef<any>();
const firstObserver = useCallback((node: HTMLDivElement) => {
@@ -147,7 +145,7 @@ function LogsTable({ logs, setPage }: LogsTableProps) {
ref={observerRef}
className="flex flex-col items-start h-[51vh] overflow-y-auto bg-transparent flex-grow gap-px"
>
{logs.map((log, index) => {
{logs?.map((log, index) => {
if (index === logs.length - 1) {
return (
<div ref={firstObserver} key={index}>
@@ -156,6 +154,7 @@ function LogsTable({ logs, setPage }: LogsTableProps) {
);
} else return <Log key={index} log={log} />;
})}
{loading && <SkeletonLoader component="logs" />}
</div>
</div>
);

View File

@@ -54,16 +54,28 @@ function Upload({
const advancedFields = schema.filter((field) => field.advanced);
return (
<>
{generalFields.map((field: FormField) => renderField(field))}
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-4">
{generalFields.map((field: FormField) => renderField(field))}
</div>
{advancedFields.length > 0 && showAdvancedOptions && (
<>
<hr className="my-4 border-[#C4C4C4]/40 border-[1px]" />
{advancedFields.map((field: FormField) => renderField(field))}
</>
{advancedFields.length > 0 && (
<div
className={`grid transition-all duration-300 ease-in-out ${
showAdvancedOptions
? 'grid-rows-[1fr] opacity-100'
: 'grid-rows-[0fr] opacity-0'
}`}
>
<div className="overflow-hidden flex flex-col gap-4">
<hr className="my-4 border-[#C4C4C4]/40 border-[1px]" />
<div className="flex flex-col gap-4">
{advancedFields.map((field: FormField) => renderField(field))}
</div>
</div>
</div>
)}
</>
</div>
);
};