This commit is contained in:
ManishMadan2882
2025-05-08 00:15:28 +05:30
parent d0a04d9801
commit ff532210f7
38 changed files with 550 additions and 495 deletions

View File

@@ -32,9 +32,9 @@ export default function Accordion({
setIsOpen(!isOpen);
};
return (
<div className={`shadow-sm overflow-hidden ${className}`}>
<div className={`overflow-hidden shadow-sm ${className}`}>
<button
className={`flex items-center justify-between w-full focus:outline-none ${titleClassName}`}
className={`flex w-full items-center justify-between focus:outline-none ${titleClassName}`}
onClick={toggleAccordion}
>
<p className="break-words">{title}</p>

View File

@@ -52,31 +52,31 @@ const Pagination: React.FC<PaginationProps> = ({
};
return (
<div className="flex items-center text-xs justify-end gap-4 mt-2 p-2 border-gray-200">
<div className="mt-2 flex items-center justify-end gap-4 border-gray-200 p-2 text-xs">
{/* Rows per page dropdown */}
<div className="flex items-center gap-2 relative">
<div className="relative flex items-center gap-2">
<span className="text-gray-900 dark:text-gray-50">
{t('pagination.rowsPerPage')}:
</span>
<div className="relative">
<button
onClick={toggleDropdown}
className="px-3 py-1 border rounded dark:bg-dark-charcoal dark:text-light-gray hover:bg-gray-200 dark:hover:bg-neutral-700"
className="rounded border px-3 py-1 hover:bg-gray-200 dark:bg-dark-charcoal dark:text-light-gray dark:hover:bg-neutral-700"
>
{rowsPerPage}
</button>
<div
className={`absolute z-50 right-0 mt-1 w-28 transform bg-white dark:bg-dark-charcoal shadow-lg ring-1 ring-black ring-opacity-5 transition-all duration-200 ease-in-out ${
className={`absolute right-0 z-50 mt-1 w-28 transform bg-white shadow-lg ring-1 ring-black ring-opacity-5 transition-all duration-200 ease-in-out dark:bg-dark-charcoal ${
isDropdownOpen
? 'scale-100 opacity-100 block'
: 'scale-95 opacity-0 hidden'
? 'block scale-100 opacity-100'
: 'hidden scale-95 opacity-0'
}`}
>
{rowsPerPageOptions.map((option) => (
<div
key={option}
onClick={() => handleSelectRowsPerPage(option)}
className={`cursor-pointer px-4 py-2 text-xs hover:bg-gray-100 dark:hover:bg-neutral-700 ${
className={`cursor-pointer px-4 py-2 text-xs hover:bg-gray-100 dark:hover:bg-neutral-700 ${
rowsPerPage === option
? 'bg-gray-100 dark:bg-neutral-700 dark:text-light-gray'
: 'bg-white dark:bg-dark-charcoal dark:text-light-gray'
@@ -97,45 +97,45 @@ const Pagination: React.FC<PaginationProps> = ({
<button
onClick={handleFirstPage}
disabled={currentPage === 1}
className="px-2 py-1 border rounded disabled:opacity-50"
className="rounded border px-2 py-1 disabled:opacity-50"
>
<img
src={DoubleArrowLeft}
alt={t('pagination.firstPage')}
className="dark:invert dark:sepia dark:brightness-200"
className="dark:brightness-200 dark:invert dark:sepia"
/>
</button>
<button
onClick={handlePreviousPage}
disabled={currentPage === 1}
className="px-2 py-1 border rounded disabled:opacity-50"
className="rounded border px-2 py-1 disabled:opacity-50"
>
<img
src={SingleArrowLeft}
alt={t('pagination.previousPage')}
className="dark:invert dark:sepia dark:brightness-200"
className="dark:brightness-200 dark:invert dark:sepia"
/>
</button>
<button
onClick={handleNextPage}
disabled={currentPage === totalPages}
className="px-2 py-1 border rounded disabled:opacity-50"
className="rounded border px-2 py-1 disabled:opacity-50"
>
<img
src={SingleArrowRight}
alt={t('pagination.nextPage')}
className="dark:invert dark:sepia dark:brightness-200"
className="dark:brightness-200 dark:invert dark:sepia"
/>
</button>
<button
onClick={handleLastPage}
disabled={currentPage === totalPages}
className="px-2 py-1 border rounded disabled:opacity-50"
className="rounded border px-2 py-1 disabled:opacity-50"
>
<img
src={DoubleArrowRight}
alt={t('pagination.lastPage')}
className="dark:invert dark:sepia dark:brightness-200"
className="dark:brightness-200 dark:invert dark:sepia"
/>
</button>
</div>

View File

@@ -88,7 +88,7 @@ export default function DropdownMenu({
onClick={(e) => e.stopPropagation()}
>
<div
className={`w-28 transform rounded-md bg-white dark:bg-dark-charcoal shadow-lg ring-1 ring-black ring-opacity-5 transition-all duration-200 ease-in-out ${className}`}
className={`w-28 transform rounded-md bg-white shadow-lg ring-1 ring-black ring-opacity-5 transition-all duration-200 ease-in-out dark:bg-dark-charcoal ${className}`}
>
<div
role="menu"

View File

@@ -36,20 +36,20 @@ const Help = () => {
<button
ref={buttonRef}
onClick={toggleDropdown}
className="my-auto mx-4 w-full flex items-center h-9 gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-[#28292E]"
className="mx-4 my-auto flex h-9 w-full items-center gap-4 rounded-3xl hover:bg-gray-100 dark:hover:bg-[#28292E]"
>
<img src={Info} alt="info" className="ml-2 w-5 filter dark:invert" />
{t('help')}
</button>
{isOpen && (
<div
className={`absolute translate-x-4 -translate-y-28 z-10 w-48 shadow-lg bg-white dark:bg-[#444654] rounded-xl`}
className={`absolute z-10 w-48 -translate-y-28 translate-x-4 rounded-xl bg-white shadow-lg dark:bg-[#444654]`}
>
<a
href="https://docs.docsgpt.cloud/"
target="_blank"
rel="noopener noreferrer"
className="flex items-start gap-4 px-4 py-2 text-black dark:text-white hover:bg-bright-gray dark:hover:bg-[#545561] rounded-t-xl"
className="flex items-start gap-4 rounded-t-xl px-4 py-2 text-black hover:bg-bright-gray dark:text-white dark:hover:bg-[#545561]"
>
<img
src={PageIcon}
@@ -61,12 +61,12 @@ const Help = () => {
</a>
<a
href="mailto:support@docsgpt.cloud"
className="flex items-start gap-4 px-4 py-2 text-black dark:text-white hover:bg-bright-gray dark:hover:bg-[#545561] rounded-b-xl"
className="flex items-start gap-4 rounded-b-xl px-4 py-2 text-black hover:bg-bright-gray dark:text-white dark:hover:bg-[#545561]"
>
<img
src={EmailIcon}
alt="Email Us"
className="filter dark:invert p-0.5"
className="p-0.5 filter dark:invert"
width={20}
/>
{t('emailUs')}

View File

@@ -2,7 +2,10 @@ import React, { useEffect, useRef, useState } from 'react';
import mermaid from 'mermaid';
import CopyButton from './CopyButton';
import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter';
import { oneLight, vscDarkPlus } from 'react-syntax-highlighter/dist/cjs/styles/prism';
import {
oneLight,
vscDarkPlus,
} from 'react-syntax-highlighter/dist/cjs/styles/prism';
import { MermaidRendererProps } from './types';
import { useSelector } from 'react-redux';
import { selectStatus } from '../conversation/conversationSlice';
@@ -20,9 +23,12 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
const [showDownloadMenu, setShowDownloadMenu] = useState<boolean>(false);
const downloadMenuRef = useRef<HTMLDivElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const [hoverPosition, setHoverPosition] = useState<{ x: number, y: number } | null>(null);
const [hoverPosition, setHoverPosition] = useState<{
x: number;
y: number;
} | null>(null);
const [isHovering, setIsHovering] = useState<boolean>(false);
const [zoomFactor, setZoomFactor] = useState<number>(2);
const [zoomFactor, setZoomFactor] = useState<number>(2);
const handleMouseMove = (event: React.MouseEvent) => {
if (!containerRef.current) return;
@@ -40,16 +46,14 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
setHoverPosition(null);
};
const handleKeyDown = (event: React.KeyboardEvent) => {
if (!isHovering) return;
if (event.key === '+' || event.key === '=') {
setZoomFactor(prev => Math.min(6, prev + 0.5)); // Cap at 6x
setZoomFactor((prev) => Math.min(6, prev + 0.5)); // Cap at 6x
event.preventDefault();
}
else if (event.key === '-') {
setZoomFactor(prev => Math.max(1, prev - 0.5)); // Minimum 1x
} else if (event.key === '-') {
setZoomFactor((prev) => Math.max(1, prev - 0.5)); // Minimum 1x
event.preventDefault();
}
};
@@ -57,13 +61,13 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
const handleWheel = (event: React.WheelEvent) => {
if (!isHovering) return;
if ( event.ctrlKey || event.metaKey) {
if (event.ctrlKey || event.metaKey) {
event.preventDefault();
if (event.deltaY < 0) {
setZoomFactor(prev => Math.min(6, prev + 0.25));
setZoomFactor((prev) => Math.min(6, prev + 0.25));
} else {
setZoomFactor(prev => Math.max(1, prev - 0.25));
setZoomFactor((prev) => Math.max(1, prev - 0.25));
}
}
};
@@ -81,8 +85,9 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
securityLevel: 'loose',
suppressErrorRendering: true,
});
const isCurrentlyLoading = isLoading !== undefined ? isLoading : status === 'loading';
const isCurrentlyLoading =
isLoading !== undefined ? isLoading : status === 'loading';
if (!isCurrentlyLoading && code) {
try {
const element = document.getElementById(diagramId.current);
@@ -93,7 +98,9 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
}
} catch (err) {
console.error('Error rendering mermaid diagram:', err);
setError(`Failed to render diagram: ${err instanceof Error ? err.message : String(err)}`);
setError(
`Failed to render diagram: ${err instanceof Error ? err.message : String(err)}`,
);
}
}
};
@@ -101,9 +108,6 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
renderDiagram();
}, [code, isDarkTheme, isLoading]);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (
@@ -120,7 +124,6 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
};
}, [showDownloadMenu]);
const downloadSvg = (): void => {
const element = document.getElementById(diagramId.current);
if (!element) return;
@@ -146,7 +149,7 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
const svgBlob = new Blob(
[`<?xml version="1.0" encoding="UTF-8" standalone="no"?>\n${svgString}`],
{ type: 'image/svg+xml' }
{ type: 'image/svg+xml' },
);
const url = URL.createObjectURL(svgBlob);
@@ -198,7 +201,7 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = function(): void {
img.onload = function (): void {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
@@ -243,20 +246,20 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
URL.revokeObjectURL(url);
};
const downloadOptions = [
{ label: 'Download as SVG', action: downloadSvg },
{ label: 'Download as PNG', action: downloadPng },
{ label: 'Download as MMD', action: downloadMmd },
];
const isCurrentlyLoading = isLoading !== undefined ? isLoading : status === 'loading';
const isCurrentlyLoading =
isLoading !== undefined ? isLoading : status === 'loading';
const showDiagramOptions = !isCurrentlyLoading && !error;
const errorRender = !isCurrentlyLoading && error;
return (
<div className="group relative rounded-lg border border-light-silver dark:border-raisin-black bg-white dark:bg-eerie-black w-inherit">
<div className="flex justify-between items-center px-2 py-1 bg-platinum dark:bg-eerie-black-2">
<div className="w-inherit group relative rounded-lg border border-light-silver bg-white dark:border-raisin-black dark:bg-eerie-black">
<div className="flex items-center justify-between bg-platinum px-2 py-1 dark:bg-eerie-black-2">
<span className="text-xs font-medium text-just-black dark:text-chinese-white">
mermaid
</span>
@@ -267,13 +270,13 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
<div className="relative" ref={downloadMenuRef}>
<button
onClick={() => setShowDownloadMenu(!showDownloadMenu)}
className="text-xs px-2 py-1 bg-gray-100 dark:bg-gray-700 rounded flex items-center h-full"
className="flex h-full items-center rounded bg-gray-100 px-2 py-1 text-xs dark:bg-gray-700"
title="Download options"
>
Download <span className="ml-1"></span>
</button>
{showDownloadMenu && (
<div className="absolute right-0 mt-1 bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded shadow-lg z-10 w-40">
<div className="absolute right-0 z-10 mt-1 w-40 rounded border border-gray-200 bg-white shadow-lg dark:border-gray-700 dark:bg-gray-800">
<ul>
{downloadOptions.map((option, index) => (
<li key={index}>
@@ -282,7 +285,7 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
option.action();
setShowDownloadMenu(false);
}}
className="text-xs px-4 py-2 w-full text-left hover:bg-gray-100 dark:hover:bg-gray-700"
className="w-full px-4 py-2 text-left text-xs hover:bg-gray-100 dark:hover:bg-gray-700"
>
{option.label}
</button>
@@ -297,7 +300,7 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
{showDiagramOptions && (
<button
onClick={() => setShowCode(!showCode)}
className={`text-xs px-2 py-1 rounded flex items-center h-full ${
className={`flex h-full items-center rounded px-2 py-1 text-xs ${
showCode
? 'bg-blue-200 dark:bg-blue-800'
: 'bg-gray-100 dark:bg-gray-700'
@@ -311,14 +314,14 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
</div>
{isCurrentlyLoading ? (
<div className="p-4 bg-white dark:bg-eerie-black flex justify-center items-center">
<div className="flex items-center justify-center bg-white p-4 dark:bg-eerie-black">
<div className="text-sm text-gray-500 dark:text-gray-400">
Loading diagram...
</div>
</div>
) : errorRender ? (
<div className="border-2 border-red-400 dark:border-red-700 rounded m-2">
<div className="bg-red-100 dark:bg-red-900/30 px-4 py-2 text-red-800 dark:text-red-300 text-sm whitespace-normal break-words overflow-auto">
<div className="m-2 rounded border-2 border-red-400 dark:border-red-700">
<div className="overflow-auto whitespace-normal break-words bg-red-100 px-4 py-2 text-sm text-red-800 dark:bg-red-900/30 dark:text-red-300">
{error}
</div>
</div>
@@ -326,13 +329,12 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
<>
<div
ref={containerRef}
className=" no-scrollbar p-4 block w-full bg-white dark:bg-eerie-black relative"
className="no-scrollbar relative block w-full bg-white p-4 dark:bg-eerie-black"
style={{
overflow: 'auto',
scrollbarWidth: 'none',
msOverflowStyle: 'none',
width: '100%',
}}
onMouseMove={handleMouseMove}
onMouseEnter={handleMouseEnter}
@@ -340,39 +342,42 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
onKeyDown={handleKeyDown}
onWheel={handleWheel}
tabIndex={0}
>
{isHovering && (
<>
<div className="absolute top-2 right-2 bg-black/70 text-white text-xs px-2 py-1 rounded z-10 flex items-center gap-2">
<button
onClick={() => setZoomFactor(prev => Math.max(1, prev - 0.5))}
className="hover:bg-gray-600 px-1 rounded"
title="Decrease zoom"
>
-
</button>
<span
className="cursor-pointer hover:underline"
onClick={() => {
setZoomFactor(2);
}}
title="Reset zoom"
>
{zoomFactor.toFixed(1)}x
</span>
<button
onClick={() => setZoomFactor(prev => Math.min(6, prev + 0.5))}
className="hover:bg-gray-600 px-1 rounded"
title="Increase zoom"
>
+
</button>
</div>
<div className="absolute right-2 top-2 z-10 flex items-center gap-2 rounded bg-black/70 px-2 py-1 text-xs text-white">
<button
onClick={() =>
setZoomFactor((prev) => Math.max(1, prev - 0.5))
}
className="rounded px-1 hover:bg-gray-600"
title="Decrease zoom"
>
-
</button>
<span
className="cursor-pointer hover:underline"
onClick={() => {
setZoomFactor(2);
}}
title="Reset zoom"
>
{zoomFactor.toFixed(1)}x
</span>
<button
onClick={() =>
setZoomFactor((prev) => Math.min(6, prev + 0.5))
}
className="rounded px-1 hover:bg-gray-600"
title="Increase zoom"
>
+
</button>
</div>
</>
)}
<pre
className="mermaid select-none w-full"
className="mermaid w-full select-none"
id={diagramId.current}
key={`mermaid-${diagramId.current}`}
style={{
@@ -382,7 +387,7 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
cursor: 'default',
width: '100%',
display: 'flex',
justifyContent: 'center'
justifyContent: 'center',
}}
>
{code}
@@ -391,7 +396,7 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
{showCode && (
<div className="border-t border-light-silver dark:border-raisin-black">
<div className="p-2 bg-platinum dark:bg-eerie-black-2">
<div className="bg-platinum p-2 dark:bg-eerie-black-2">
<span className="text-xs font-medium text-just-black dark:text-chinese-white">
Mermaid Code
</span>
@@ -403,7 +408,7 @@ const MermaidRenderer: React.FC<MermaidRendererProps> = ({
margin: 0,
borderRadius: 0,
scrollbarWidth: 'thin',
maxHeight: '300px'
maxHeight: '300px',
}}
>
{code}

View File

@@ -15,7 +15,7 @@ export default function ShareButton({ conversationId }: ShareButtonProps) {
onClick={() => {
setShareModalState(true);
}}
className="absolute top-4 right-20 z-20 rounded-full hover:bg-bright-gray dark:hover:bg-[#28292E]"
className="absolute right-20 top-4 z-20 rounded-full hover:bg-bright-gray dark:hover:bg-[#28292E]"
>
<img
className="m-2 h-5 w-5 filter dark:invert"

View File

@@ -45,7 +45,7 @@ export default function Sidebar({
<img className="filter dark:invert" src={Exit} />
</button>
</div>
<div className="flex h-full flex-col items-center gap-2 py-4 px-6 text-center">
<div className="flex h-full flex-col items-center gap-2 px-6 py-4 text-center">
{children}
</div>
</div>

View File

@@ -42,17 +42,17 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
<>
{[...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 className="w-[45%] px-4 py-4">
<div className="h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></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 className="w-[20%] px-4 py-4">
<div className="h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></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 className="w-[25%] px-4 py-4">
<div className="h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></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 className="w-[10%] px-4 py-4">
<div className="h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></div>
</td>
</tr>
))}
@@ -64,16 +64,16 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
{[...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>
<div className="mx-auto h-4 w-3/4 rounded bg-gray-300 dark:bg-gray-600"></div>
</td>
<td className="p-2">
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-full mx-auto"></div>
<div className="mx-auto h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></div>
</td>
<td className="p-2">
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-full mx-auto"></div>
<div className="mx-auto h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></div>
</td>
<td className="p-2">
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-8 mx-auto"></div>
<div className="mx-auto h-4 w-8 rounded bg-gray-300 dark:bg-gray-600"></div>
</td>
</tr>
))}
@@ -82,10 +82,10 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
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 className="mb-2 h-4 w-24 rounded bg-gray-300 dark:bg-gray-600"></div>
<div className="flex h-14 w-[360px] items-center justify-between rounded-3xl bg-gray-300 px-4 dark:bg-gray-600">
<div className="h-3 w-24 rounded bg-gray-400 dark:bg-gray-700"></div>
<div className="h-3 w-3 rounded bg-gray-400 dark:bg-gray-700"></div>
</div>
</div>
);
@@ -95,14 +95,14 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
{[...Array(8)].map((_, idx) => (
<div
key={idx}
className="w-full flex items-start p-2 hover:bg-[#F9F9F9] hover:dark:bg-dark-charcoal"
className="flex w-full items-start p-2 hover:bg-[#F9F9F9] hover:dark:bg-dark-charcoal"
>
<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 className="flex w-full items-center gap-2">
<div className="h-3 w-3 rounded-lg bg-gray-300 dark:bg-gray-600"></div>
<div className="flex w-full flex-row items-center gap-2">
<div className="h-3 w-[30%] rounded-lg bg-gray-300 dark:bg-gray-600 lg:w-52"></div>
<div className="h-3 w-[16%] rounded-lg bg-gray-300 dark:bg-gray-600 lg:w-28"></div>
<div className="h-3 w-[40%] rounded-lg bg-gray-300 dark:bg-gray-600 lg:w-64"></div>
</div>
</div>
</div>
@@ -117,32 +117,32 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
key={idx}
className={`p-6 ${
skeletonCount === 1 ? 'w-full' : 'w-60'
} dark:bg-raisin-black rounded-3xl animate-pulse`}
} animate-pulse rounded-3xl dark:bg-raisin-black`}
>
<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 className="mb-2 h-4 w-3/4 rounded bg-gray-300 dark:bg-gray-600"></div>
<div className="mb-2 h-4 w-5/6 rounded bg-gray-300 dark:bg-gray-600"></div>
<div className="mb-2 h-4 w-1/2 rounded bg-gray-300 dark:bg-gray-600"></div>
<div className="mb-2 h-4 w-3/4 rounded bg-gray-300 dark:bg-gray-600"></div>
<div className="mb-2 h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></div>
</div>
<div className="border-t border-gray-400 dark:border-gray-700 my-4"></div>
<div className="my-4 border-t border-gray-400 dark:border-gray-700"></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 className="mb-2 h-4 w-2/3 rounded bg-gray-300 dark:bg-gray-600"></div>
<div className="mb-2 h-4 w-1/4 rounded bg-gray-300 dark:bg-gray-600"></div>
<div className="mb-2 h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></div>
</div>
<div className="border-t border-gray-400 dark:border-gray-700 my-4"></div>
<div className="my-4 border-t border-gray-400 dark:border-gray-700"></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 className="mb-2 h-4 w-5/6 rounded bg-gray-300 dark:bg-gray-600"></div>
<div className="mb-2 h-4 w-1/3 rounded bg-gray-300 dark:bg-gray-600"></div>
<div className="mb-2 h-4 w-2/3 rounded bg-gray-300 dark:bg-gray-600"></div>
<div className="mb-2 h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></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 className="my-4 border-t border-gray-400 dark:border-gray-700"></div>
<div className="mb-2 h-4 w-3/4 rounded bg-gray-300 dark:bg-gray-600"></div>
<div className="mb-2 h-4 w-5/6 rounded bg-gray-300 dark:bg-gray-600"></div>
</div>
</div>
))}
@@ -154,27 +154,27 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
{[...Array(skeletonCount)].map((_, idx) => (
<div
key={idx}
className="p-6 w-full dark:bg-raisin-black rounded-3xl animate-pulse"
className="w-full animate-pulse rounded-3xl p-6 dark:bg-raisin-black"
>
<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 className="mb-4 h-4 w-1/3 rounded bg-gray-300 dark:bg-gray-600"></div>
<div className="grid grid-cols-6 items-end gap-2">
<div className="h-32 rounded bg-gray-300 dark:bg-gray-600"></div>
<div className="h-24 rounded bg-gray-300 dark:bg-gray-600"></div>
<div className="h-40 rounded bg-gray-300 dark:bg-gray-600"></div>
<div className="h-28 rounded bg-gray-300 dark:bg-gray-600"></div>
<div className="h-36 rounded bg-gray-300 dark:bg-gray-600"></div>
<div className="h-20 rounded bg-gray-300 dark:bg-gray-600"></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 className="mb-4 h-4 w-1/4 rounded bg-gray-300 dark:bg-gray-600"></div>
<div className="h-32 rounded bg-gray-300 dark:bg-gray-600"></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 className="h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></div>
<div className="h-4 w-full rounded bg-gray-300 dark:bg-gray-600"></div>
</div>
</div>
</div>

View File

@@ -32,8 +32,13 @@ export default function SourcesPopup({
const { t } = useTranslation();
const popupRef = useRef<HTMLDivElement>(null);
const [searchTerm, setSearchTerm] = useState('');
const [popupPosition, setPopupPosition] = useState({ top: 0, left: 0, maxHeight: 0, showAbove: false });
const [popupPosition, setPopupPosition] = useState({
top: 0,
left: 0,
maxHeight: 0,
showAbove: false,
});
const embeddingsName =
import.meta.env.VITE_EMBEDDINGS_NAME ||
'huggingface_sentence-transformers/all-mpnet-base-v2';
@@ -41,16 +46,16 @@ export default function SourcesPopup({
const options = useSelector(selectSourceDocs);
const selectedDocs = useSelector(selectSelectedDocs);
const filteredOptions = options?.filter(option =>
option.name.toLowerCase().includes(searchTerm.toLowerCase())
const filteredOptions = options?.filter((option) =>
option.name.toLowerCase().includes(searchTerm.toLowerCase()),
);
useLayoutEffect(() => {
if (!isOpen || !anchorRef.current) return;
const updatePosition = () => {
if (!anchorRef.current) return;
const rect = anchorRef.current.getBoundingClientRect();
const viewportHeight = window.innerHeight;
const viewportWidth = window.innerWidth;
@@ -60,17 +65,17 @@ export default function SourcesPopup({
const maxHeight = showAbove ? spaceAbove - 16 : spaceBelow - 16;
const left = Math.min(
rect.left,
viewportWidth - Math.min(480, viewportWidth * 0.95) - 10
viewportWidth - Math.min(480, viewportWidth * 0.95) - 10,
);
setPopupPosition({
top: showAbove ? rect.top - 8 : rect.bottom + 8,
left,
maxHeight: Math.min(600, maxHeight),
showAbove
showAbove,
});
};
updatePosition();
window.addEventListener('resize', updatePosition);
return () => window.removeEventListener('resize', updatePosition);
@@ -111,10 +116,12 @@ export default function SourcesPopup({
return (
<div
ref={popupRef}
className="fixed z-50 bg-lotion dark:bg-charleston-green-2 rounded-xl shadow-[0px_9px_46px_8px_#0000001F,0px_24px_38px_3px_#00000024,0px_11px_15px_-7px_#00000033] flex flex-col"
className="fixed z-50 flex flex-col rounded-xl bg-lotion shadow-[0px_9px_46px_8px_#0000001F,0px_24px_38px_3px_#00000024,0px_11px_15px_-7px_#00000033] dark:bg-charleston-green-2"
style={{
top: popupPosition.showAbove ? popupPosition.top : undefined,
bottom: popupPosition.showAbove ? undefined : window.innerHeight - popupPosition.top,
bottom: popupPosition.showAbove
? undefined
: window.innerHeight - popupPosition.top,
left: popupPosition.left,
maxWidth: Math.min(480, window.innerWidth * 0.95),
width: '100%',
@@ -122,12 +129,12 @@ export default function SourcesPopup({
transform: popupPosition.showAbove ? 'translateY(-100%)' : 'none',
}}
>
<div className="flex flex-col h-full">
<div className="px-4 md:px-6 py-4 flex-shrink-0">
<h2 className="text-lg font-bold text-[#141414] dark:text-bright-gray mb-4 dark:text-[20px]">
<div className="flex h-full flex-col">
<div className="flex-shrink-0 px-4 py-4 md:px-6">
<h2 className="mb-4 text-lg font-bold text-[#141414] dark:text-[20px] dark:text-bright-gray">
{t('conversation.sources.text')}
</h2>
<Input
id="source-search"
name="source-search"
@@ -141,7 +148,7 @@ export default function SourcesPopup({
/>
</div>
<div className="flex-grow overflow-y-auto mx-4 border border-[#D9D9D9] dark:border-dim-gray rounded-md [&::-webkit-scrollbar-thumb]:bg-[#888] [&::-webkit-scrollbar-thumb]:hover:bg-[#555] [&::-webkit-scrollbar-track]:bg-[#E2E8F0] dark:[&::-webkit-scrollbar-track]:bg-[#2C2E3C]">
<div className="mx-4 flex-grow overflow-y-auto rounded-md border border-[#D9D9D9] dark:border-dim-gray [&::-webkit-scrollbar-thumb]:bg-[#888] [&::-webkit-scrollbar-thumb]:hover:bg-[#555] [&::-webkit-scrollbar-track]:bg-[#E2E8F0] dark:[&::-webkit-scrollbar-track]:bg-[#2C2E3C]">
{options ? (
<>
{filteredOptions?.map((option: any, index: number) => {
@@ -149,7 +156,7 @@ export default function SourcesPopup({
return (
<div
key={index}
className="flex cursor-pointer items-center p-3 hover:bg-gray-100 dark:hover:bg-[#2C2E3C] transition-colors border-b border-[#D9D9D9] dark:border-dim-gray border-opacity-80 dark:text-[14px]"
className="flex cursor-pointer items-center border-b border-[#D9D9D9] border-opacity-80 p-3 transition-colors hover:bg-gray-100 dark:border-dim-gray dark:text-[14px] dark:hover:bg-[#2C2E3C]"
onClick={() => {
dispatch(setSelectedDocs(option));
handlePostDocumentSelect(option);
@@ -159,23 +166,26 @@ export default function SourcesPopup({
<img
src={SourceIcon}
alt="Source"
width={14} height={14}
width={14}
height={14}
className="mr-3 flex-shrink-0"
/>
<span className="text-[#5D5D5D] dark:text-bright-gray font-medium flex-grow overflow-hidden overflow-ellipsis whitespace-nowrap mr-3">
<span className="mr-3 flex-grow overflow-hidden overflow-ellipsis whitespace-nowrap font-medium text-[#5D5D5D] dark:text-bright-gray">
{option.name}
</span>
<div className={`w-4 h-4 border flex-shrink-0 flex items-center justify-center p-[0.5px] dark:border-[#757783] border-[#C6C6C6]`}>
{selectedDocs &&
(option.id ?
selectedDocs.id === option.id : // For documents with MongoDB IDs
selectedDocs.date === option.date) && // For preloaded sources
<img
src={CheckIcon}
alt="Selected"
className="h-3 w-3"
/>
}
<div
className={`flex h-4 w-4 flex-shrink-0 items-center justify-center border border-[#C6C6C6] p-[0.5px] dark:border-[#757783]`}
>
{selectedDocs &&
(option.id
? selectedDocs.id === option.id // For documents with MongoDB IDs
: selectedDocs.date === option.date) && ( // For preloaded sources
<img
src={CheckIcon}
alt="Selected"
className="h-3 w-3"
/>
)}
</div>
</div>
);
@@ -183,14 +193,22 @@ export default function SourcesPopup({
return null;
})}
<div
className="flex cursor-pointer items-center p-3 hover:bg-gray-100 dark:hover:bg-[#2C2E3C] transition-colors border-b border-[#D9D9D9] dark:border-dim-gray border-opacity-80 dark:text-[14px]"
className="flex cursor-pointer items-center border-b border-[#D9D9D9] border-opacity-80 p-3 transition-colors hover:bg-gray-100 dark:border-dim-gray dark:text-[14px] dark:hover:bg-[#2C2E3C]"
onClick={handleEmptyDocumentSelect}
>
<img width={14} height={14} src={SourceIcon} alt="Source" className="mr-3 flex-shrink-0" />
<span className="text-[#5D5D5D] dark:text-bright-gray font-medium flex-grow mr-3">
<img
width={14}
height={14}
src={SourceIcon}
alt="Source"
className="mr-3 flex-shrink-0"
/>
<span className="mr-3 flex-grow font-medium text-[#5D5D5D] dark:text-bright-gray">
{t('none')}
</span>
<div className={`w-4 h-4 border flex-shrink-0 flex items-center justify-center p-[0.5px] dark:border-[#757783] border-[#C6C6C6]`}>
<div
className={`flex h-4 w-4 flex-shrink-0 items-center justify-center border border-[#C6C6C6] p-[0.5px] dark:border-[#757783]`}
>
{selectedDocs === null && (
<img src={CheckIcon} alt="Selected" className="h-3 w-3" />
)}
@@ -198,27 +216,27 @@ export default function SourcesPopup({
</div>
</>
) : (
<div className="p-4 text-center text-gray-500 dark:text-bright-gray dark:text-[14px]">
<div className="p-4 text-center text-gray-500 dark:text-[14px] dark:text-bright-gray">
{t('noSourcesAvailable')}
</div>
)}
</div>
<div className="px-4 md:px-6 py-4 opacity-75 hover:opacity-100 transition-opacity duration-200 flex-shrink-0">
<a
href="/settings/documents"
className="text-violets-are-blue text-base font-medium inline-flex items-center gap-2"
<div className="flex-shrink-0 px-4 py-4 opacity-75 transition-opacity duration-200 hover:opacity-100 md:px-6">
<a
href="/settings/documents"
className="inline-flex items-center gap-2 text-base font-medium text-violets-are-blue"
onClick={onClose}
>
Go to Documents
<img src={RedirectIcon} alt="Redirect" className="w-3 h-3" />
<img src={RedirectIcon} alt="Redirect" className="h-3 w-3" />
</a>
</div>
<div className="px-4 md:px-6 py-3 flex justify-start flex-shrink-0">
<div className="flex flex-shrink-0 justify-start px-4 py-3 md:px-6">
<button
onClick={handleUploadClick}
className="py-2 px-4 rounded-full border text-violets-are-blue hover:bg-violets-are-blue border-violets-are-blue hover:text-white transition-colors duration-200 text-[14px] font-medium w-auto"
className="w-auto rounded-full border border-violets-are-blue px-4 py-2 text-[14px] font-medium text-violets-are-blue transition-colors duration-200 hover:bg-violets-are-blue hover:text-white"
>
Upload new
</button>

View File

@@ -46,9 +46,9 @@ const ToggleSwitch: React.FC<ToggleSwitchProps> = ({
return (
<label
className={`cursor-pointer select-none flex flex-row items-center ${
className={`flex cursor-pointer select-none flex-row items-center ${
labelPosition === 'right' ? 'flex-row-reverse' : ''
} ${disabled ? 'opacity-50 cursor-not-allowed' : ''} ${className}`}
} ${disabled ? 'cursor-not-allowed opacity-50' : ''} ${className}`}
>
{label && (
<span
@@ -75,7 +75,7 @@ const ToggleSwitch: React.FC<ToggleSwitchProps> = ({
}`}
></div>
<div
className={`absolute ${toggle} flex items-center justify-center rounded-full transition bg-white opacity-80 ${
className={`absolute ${toggle} flex items-center justify-center rounded-full bg-white opacity-80 transition ${
checked ? `${translate} bg-silver` : ''
}`}
></div>

View File

@@ -29,18 +29,23 @@ export default function ToolsPopup({
const [searchTerm, setSearchTerm] = useState('');
const [isDarkTheme] = useDarkTheme();
const popupRef = useRef<HTMLDivElement>(null);
const [popupPosition, setPopupPosition] = useState({ top: 0, left: 0, maxHeight: 0, showAbove: false });
const [popupPosition, setPopupPosition] = useState({
top: 0,
left: 0,
maxHeight: 0,
showAbove: false,
});
useLayoutEffect(() => {
if (!isOpen || !anchorRef.current) return;
const updatePosition = () => {
if (!anchorRef.current) return;
const rect = anchorRef.current.getBoundingClientRect();
const viewportHeight = window.innerHeight;
const viewportWidth = window.innerWidth;
const spaceAbove = rect.top;
const spaceBelow = viewportHeight - rect.bottom;
const showAbove = spaceAbove > spaceBelow && spaceAbove >= 300;
@@ -48,17 +53,17 @@ export default function ToolsPopup({
const left = Math.min(
rect.left,
viewportWidth - Math.min(462, viewportWidth * 0.95) - 10
viewportWidth - Math.min(462, viewportWidth * 0.95) - 10,
);
setPopupPosition({
top: showAbove ? rect.top - 8 : rect.bottom + 8,
left,
maxHeight: Math.min(600, maxHeight),
showAbove
showAbove,
});
};
updatePosition();
window.addEventListener('resize', updatePosition);
return () => window.removeEventListener('resize', updatePosition);
@@ -125,16 +130,18 @@ export default function ToolsPopup({
if (!isOpen) return null;
const filteredTools = userTools.filter((tool) =>
tool.displayName.toLowerCase().includes(searchTerm.toLowerCase())
tool.displayName.toLowerCase().includes(searchTerm.toLowerCase()),
);
return (
<div
ref={popupRef}
className="fixed z-[9999] rounded-lg border border-light-silver dark:border-dim-gray bg-lotion dark:bg-charleston-green-2 shadow-[0px_9px_46px_8px_#0000001F,0px_24px_38px_3px_#00000024,0px_11px_15px_-7px_#00000033]"
className="fixed z-[9999] rounded-lg border border-light-silver bg-lotion shadow-[0px_9px_46px_8px_#0000001F,0px_24px_38px_3px_#00000024,0px_11px_15px_-7px_#00000033] dark:border-dim-gray dark:bg-charleston-green-2"
style={{
top: popupPosition.showAbove ? popupPosition.top : undefined,
bottom: popupPosition.showAbove ? undefined : window.innerHeight - popupPosition.top,
bottom: popupPosition.showAbove
? undefined
: window.innerHeight - popupPosition.top,
left: popupPosition.left,
maxWidth: Math.min(462, window.innerWidth * 0.95),
width: '100%',
@@ -142,9 +149,9 @@ export default function ToolsPopup({
transform: popupPosition.showAbove ? 'translateY(-100%)' : 'none',
}}
>
<div className="flex flex-col h-full">
<div className="p-4 flex-shrink-0">
<h3 className="text-lg font-medium text-gray-900 dark:text-white mb-4">
<div className="flex h-full flex-col">
<div className="flex-shrink-0 p-4">
<h3 className="mb-4 text-lg font-medium text-gray-900 dark:text-white">
{t('settings.tools.label')}
</h3>
@@ -162,20 +169,20 @@ export default function ToolsPopup({
</div>
{loading ? (
<div className="flex justify-center py-4 flex-grow">
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-gray-900 dark:border-white"></div>
<div className="flex flex-grow justify-center py-4">
<div className="h-6 w-6 animate-spin rounded-full border-b-2 border-gray-900 dark:border-white"></div>
</div>
) : (
<div className="mx-4 border border-[#D9D9D9] dark:border-dim-gray rounded-md overflow-hidden flex-grow">
<div className="mx-4 flex-grow overflow-hidden rounded-md border border-[#D9D9D9] dark:border-dim-gray">
<div className="h-full overflow-y-auto [&::-webkit-scrollbar-thumb]:bg-[#888] [&::-webkit-scrollbar-thumb]:hover:bg-[#555] [&::-webkit-scrollbar-track]:bg-[#E2E8F0] dark:[&::-webkit-scrollbar-track]:bg-[#2C2E3C]">
{filteredTools.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full py-8">
<div className="flex h-full flex-col items-center justify-center py-8">
<img
src={isDarkTheme ? NoFilesDarkIcon : NoFilesIcon}
alt="No tools found"
className="h-24 w-24 mx-auto mb-4"
className="mx-auto mb-4 h-24 w-24"
/>
<p className="text-gray-500 dark:text-gray-400 text-center">
<p className="text-center text-gray-500 dark:text-gray-400">
{t('settings.tools.noToolsFound')}
</p>
</div>
@@ -184,22 +191,24 @@ export default function ToolsPopup({
<div
key={tool.id}
onClick={() => updateToolStatus(tool.id, !tool.status)}
className="flex items-center justify-between p-3 border-b border-[#D9D9D9] dark:border-dim-gray hover:bg-gray-100 dark:hover:bg-charleston-green-3"
className="flex items-center justify-between border-b border-[#D9D9D9] p-3 hover:bg-gray-100 dark:border-dim-gray dark:hover:bg-charleston-green-3"
>
<div className="flex items-center flex-grow mr-3">
<div className="mr-3 flex flex-grow items-center">
<img
src={`/toolIcons/tool_${tool.name}.svg`}
alt={`${tool.displayName} icon`}
className="h-5 w-5 mr-4 flex-shrink-0"
className="mr-4 h-5 w-5 flex-shrink-0"
/>
<div className="overflow-hidden">
<p className="text-xs font-medium text-gray-900 dark:text-white overflow-hidden overflow-ellipsis whitespace-nowrap">
<p className="overflow-hidden overflow-ellipsis whitespace-nowrap text-xs font-medium text-gray-900 dark:text-white">
{tool.displayName}
</p>
</div>
</div>
<div className="flex items-center flex-shrink-0">
<div className={`w-4 h-4 border flex items-center justify-center p-[0.5px] dark:border-[#757783] border-[#C6C6C6]`}>
<div className="flex flex-shrink-0 items-center">
<div
className={`flex h-4 w-4 items-center justify-center border border-[#C6C6C6] p-[0.5px] dark:border-[#757783]`}
>
{tool.status && (
<img
src={CheckmarkIcon}
@@ -217,10 +226,10 @@ export default function ToolsPopup({
</div>
)}
<div className="p-4 flex-shrink-0 opacity-75 hover:opacity-100 transition-opacity duration-200">
<div className="flex-shrink-0 p-4 opacity-75 transition-opacity duration-200 hover:opacity-100">
<a
href="/settings/tools"
className="text-base text-purple-30 font-medium inline-flex items-center"
className="inline-flex items-center text-base font-medium text-purple-30"
>
{t('settings.tools.manageTools')}
<img