mirror of
https://github.com/tiennm99/DocsGPT.git
synced 2026-10-12 22:14:05 +00:00
Merge pull request #2347 from ManishMadan2882/main
Minor frontend updates
This commit is contained in:
4 files changed
+83
-18
No files matched your search
@@ -78,6 +78,7 @@ function Dropdown<T extends DropdownOption>({
|
||||
const searchRef = useRef<HTMLInputElement>(null);
|
||||
const [open, setOpen] = useState(false);
|
||||
const [query, setQuery] = useState('');
|
||||
const [dropUp, setDropUp] = useState(false);
|
||||
|
||||
const radius = rounded === '3xl' ? 'rounded-3xl' : 'rounded-xl';
|
||||
const radiusTop = rounded === '3xl' ? 'rounded-t-3xl' : 'rounded-t-xl';
|
||||
@@ -90,14 +91,23 @@ function Dropdown<T extends DropdownOption>({
|
||||
setQuery('');
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousedown', handler);
|
||||
return () => document.removeEventListener('mousedown', handler);
|
||||
document.addEventListener('mousedown', handler, true);
|
||||
return () => document.removeEventListener('mousedown', handler, true);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (open && searchable && searchRef.current) searchRef.current.focus();
|
||||
}, [open, searchable]);
|
||||
|
||||
const handleToggle = () => {
|
||||
if (!open && ref.current) {
|
||||
const rect = ref.current.getBoundingClientRect();
|
||||
const spaceBelow = window.innerHeight - rect.bottom;
|
||||
setDropUp(spaceBelow < 220);
|
||||
}
|
||||
setOpen((v) => !v);
|
||||
};
|
||||
|
||||
const filtered = useMemo(() => {
|
||||
if (!searchable || !query.trim()) return options;
|
||||
const q = query.toLowerCase();
|
||||
@@ -110,8 +120,8 @@ function Dropdown<T extends DropdownOption>({
|
||||
<div className={`relative ${size}`} ref={ref}>
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
className={`border-border bg-card text-foreground flex w-full cursor-pointer items-center justify-between border px-5 py-3 ${open ? radiusTop : radius}`}
|
||||
onClick={handleToggle}
|
||||
className={`border-border bg-card text-foreground flex w-full cursor-pointer items-center justify-between border px-5 py-3 ${open ? (dropUp ? radiusBottom : radiusTop) : radius}`}
|
||||
>
|
||||
<span
|
||||
className={`truncate ${contentSize} ${displayValue ? '' : 'text-muted-foreground'}`}
|
||||
@@ -125,7 +135,11 @@ function Dropdown<T extends DropdownOption>({
|
||||
|
||||
{open && (
|
||||
<div
|
||||
className={`border-border bg-card absolute inset-x-0 z-20 -mt-px overflow-hidden border border-t-0 shadow-lg ${radiusBottom}`}
|
||||
className={`border-border bg-card absolute inset-x-0 z-20 overflow-hidden border shadow-lg ${
|
||||
dropUp
|
||||
? `bottom-full -mt-px border-b-0 ${radiusTop}`
|
||||
: `-mt-px border-t-0 ${radiusBottom}`
|
||||
}`}
|
||||
>
|
||||
{searchable && (
|
||||
<div className="flex items-center px-3 py-2">
|
||||
|
||||
@@ -10,7 +10,9 @@ interface SkeletonLoaderProps {
|
||||
| 'chatbot'
|
||||
| 'dropdown'
|
||||
| 'chunkCards'
|
||||
| 'sourceCards';
|
||||
| 'sourceCards'
|
||||
| 'toolCards'
|
||||
| 'addToolCards';
|
||||
}
|
||||
|
||||
const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
|
||||
@@ -237,6 +239,55 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
|
||||
</>
|
||||
);
|
||||
|
||||
const renderAddToolCards = () => (
|
||||
<>
|
||||
{Array.from({ length: count }).map((_, idx) => (
|
||||
<div
|
||||
key={`add-tool-skel-${idx}`}
|
||||
className="border-light-gainsboro dark:border-arsenic flex h-52 w-full animate-pulse flex-col justify-between rounded-2xl border p-6"
|
||||
>
|
||||
<div className="w-full">
|
||||
<div className="flex w-full items-center justify-between px-1">
|
||||
<div className="h-6 w-6 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
</div>
|
||||
<div className="mt-[9px] space-y-2 px-1">
|
||||
<div className="h-4 w-2/3 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-3 w-full rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
<div className="h-3 w-5/6 rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
<div className="h-3 w-3/4 rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
const renderToolCards = () => (
|
||||
<>
|
||||
{Array.from({ length: count }).map((_, idx) => (
|
||||
<div
|
||||
key={`tool-skel-${idx}`}
|
||||
className="bg-muted flex h-52 w-[300px] animate-pulse flex-col justify-between rounded-2xl p-6"
|
||||
>
|
||||
<div className="w-full">
|
||||
<div className="flex items-center gap-2 px-1">
|
||||
<div className="h-6 w-6 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
</div>
|
||||
<div className="mt-[9px] space-y-2 px-1">
|
||||
<div className="h-4 w-2/3 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-3 w-full rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
<div className="h-3 w-5/6 rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
<div className="h-3 w-3/4 rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end">
|
||||
<div className="h-5 w-9 rounded-full bg-gray-300 dark:bg-gray-600"></div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
|
||||
const componentMap = {
|
||||
fileTable: renderTable,
|
||||
chatbot: renderChatbot,
|
||||
@@ -246,6 +297,8 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
|
||||
analysis: renderAnalysis,
|
||||
chunkCards: renderChunkCards,
|
||||
sourceCards: renderSourceCards,
|
||||
toolCards: renderToolCards,
|
||||
addToolCards: renderAddToolCards,
|
||||
};
|
||||
|
||||
const render = componentMap[component] || componentMap.default;
|
||||
|
||||
@@ -3,8 +3,8 @@ import { useTranslation } from 'react-i18next';
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
import userService from '../api/services/userService';
|
||||
import Spinner from '../components/Spinner';
|
||||
import { useOutsideAlerter } from '../hooks';
|
||||
import SkeletonLoader from '../components/SkeletonLoader';
|
||||
import { useLoaderState, useOutsideAlerter } from '../hooks';
|
||||
import { ActiveState } from '../models/misc';
|
||||
import { selectToken } from '../preferences/preferenceSlice';
|
||||
import ConfigToolModal from './ConfigToolModal';
|
||||
@@ -37,7 +37,7 @@ export default function AddToolModal({
|
||||
React.useState<ActiveState>('INACTIVE');
|
||||
const [mcpModalState, setMcpModalState] =
|
||||
React.useState<ActiveState>('INACTIVE');
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [loading, setLoading] = useLoaderState(false);
|
||||
|
||||
useOutsideAlerter(modalRef, () => {
|
||||
if (modalState === 'ACTIVE') {
|
||||
@@ -121,8 +121,8 @@ export default function AddToolModal({
|
||||
</h2>
|
||||
<div className="mt-5 h-[73vh] overflow-auto px-3 py-px">
|
||||
{loading ? (
|
||||
<div className="flex h-full items-center justify-center">
|
||||
<Spinner />
|
||||
<div className="grid auto-rows-fr grid-cols-1 gap-4 pb-2 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<SkeletonLoader component="addToolCards" count={6} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="grid auto-rows-fr grid-cols-1 gap-4 pb-2 sm:grid-cols-2 lg:grid-cols-3">
|
||||
|
||||
@@ -10,9 +10,9 @@ import NoFilesIcon from '../assets/no-files.svg';
|
||||
import SearchIcon from '../assets/search.svg';
|
||||
import ThreeDotsIcon from '../assets/three-dots.svg';
|
||||
import ContextMenu, { MenuOption } from '../components/ContextMenu';
|
||||
import Spinner from '../components/Spinner';
|
||||
import SkeletonLoader from '../components/SkeletonLoader';
|
||||
import ToggleSwitch from '../components/ToggleSwitch';
|
||||
import { useDarkTheme } from '../hooks';
|
||||
import { useDarkTheme, useLoaderState } from '../hooks';
|
||||
import AddToolModal from '../modals/AddToolModal';
|
||||
import ConfirmationModal from '../modals/ConfirmationModal';
|
||||
import MCPServerModal from '../modals/MCPServerModal';
|
||||
@@ -33,7 +33,7 @@ export default function Tools() {
|
||||
const [selectedTool, setSelectedTool] = React.useState<
|
||||
UserToolType | APIToolType | null
|
||||
>(null);
|
||||
const [loading, setLoading] = React.useState(false);
|
||||
const [loading, setLoading] = useLoaderState(false);
|
||||
const [activeMenuId, setActiveMenuId] = React.useState<string | null>(null);
|
||||
const menuRefs = React.useRef<{
|
||||
[key: string]: React.RefObject<HTMLDivElement | null>;
|
||||
@@ -242,10 +242,8 @@ export default function Tools() {
|
||||
</div>
|
||||
<div className="border-border dark:border-border mt-5 mb-8 border-b" />
|
||||
{loading ? (
|
||||
<div className="grid grid-cols-2 gap-6 lg:grid-cols-3">
|
||||
<div className="col-span-2 mt-24 flex h-32 items-center justify-center lg:col-span-3">
|
||||
<Spinner />
|
||||
</div>
|
||||
<div className="flex flex-wrap justify-center gap-4 sm:justify-start">
|
||||
<SkeletonLoader component="toolCards" count={6} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex flex-wrap justify-center gap-4 sm:justify-start">
|
||||
|
||||
Reference in new issue
Block a user