mirror of
https://github.com/arc53/DocsGPT.git
synced 2025-11-30 00:53:14 +00:00
(lint) fe
This commit is contained in:
@@ -33,7 +33,7 @@ function MainLayout() {
|
||||
const [navOpen, setNavOpen] = useState(!(isMobile || isTablet));
|
||||
|
||||
return (
|
||||
<div className="relative h-screen overflow-hidden dark:bg-raisin-black">
|
||||
<div className="dark:bg-raisin-black relative h-screen overflow-hidden">
|
||||
<Navigation navOpen={navOpen} setNavOpen={setNavOpen} />
|
||||
<ActionButtons showNewChat={true} showShare={true} />
|
||||
<div
|
||||
|
||||
@@ -2,11 +2,11 @@ import { Link } from 'react-router-dom';
|
||||
|
||||
export default function PageNotFound() {
|
||||
return (
|
||||
<div className="grid min-h-screen dark:bg-raisin-black">
|
||||
<p className="mx-auto my-auto mt-20 flex w-full max-w-6xl flex-col place-items-center gap-6 rounded-3xl bg-gray-100 p-6 text-jet dark:bg-outer-space dark:text-gray-100 lg:p-10 xl:p-16">
|
||||
<div className="dark:bg-raisin-black grid min-h-screen">
|
||||
<p className="text-jet dark:bg-outer-space mx-auto my-auto mt-20 flex w-full max-w-6xl flex-col place-items-center gap-6 rounded-3xl bg-gray-100 p-6 lg:p-10 xl:p-16 dark:text-gray-100">
|
||||
<h1>404</h1>
|
||||
<p>The page you are looking for does not exist.</p>
|
||||
<button className="pointer-cursor mr-4 flex cursor-pointer items-center justify-center rounded-full bg-blue-1000 px-4 py-2 text-white transition-colors duration-100 hover:bg-blue-3000">
|
||||
<button className="pointer-cursor bg-blue-1000 hover:bg-blue-3000 mr-4 flex cursor-pointer items-center justify-center rounded-full px-4 py-2 text-white transition-colors duration-100">
|
||||
<Link to="/">Go Back Home</Link>
|
||||
</button>
|
||||
</p>
|
||||
|
||||
@@ -41,13 +41,13 @@ export default function ActionButtons({
|
||||
navigate('/');
|
||||
};
|
||||
return (
|
||||
<div className="fixed right-4 top-0 z-10 flex h-16 flex-col justify-center">
|
||||
<div className="fixed top-0 right-4 z-10 flex h-16 flex-col justify-center">
|
||||
<div className={`flex items-center gap-2 sm:gap-4 ${className}`}>
|
||||
{showNewChat && (
|
||||
<button
|
||||
title="Open New Chat"
|
||||
onClick={newChat}
|
||||
className="flex items-center gap-1 rounded-full p-2 hover:bg-bright-gray dark:hover:bg-[#28292E] lg:hidden"
|
||||
className="hover:bg-bright-gray flex items-center gap-1 rounded-full p-2 lg:hidden dark:hover:bg-[#28292E]"
|
||||
>
|
||||
<img
|
||||
className="filter dark:invert"
|
||||
@@ -64,7 +64,7 @@ export default function ActionButtons({
|
||||
<button
|
||||
title="Share"
|
||||
onClick={() => setShareModalState(true)}
|
||||
className="rounded-full p-2 hover:bg-bright-gray dark:hover:bg-[#28292E]"
|
||||
className="hover:bg-bright-gray rounded-full p-2 dark:hover:bg-[#28292E]"
|
||||
>
|
||||
<img
|
||||
className="filter dark:invert"
|
||||
|
||||
@@ -2,12 +2,16 @@ import React, { useState, useEffect, useRef } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { selectToken } from '../preferences/preferenceSlice';
|
||||
import { useDarkTheme, useLoaderState, useMediaQuery, useOutsideAlerter } from '../hooks';
|
||||
import {
|
||||
useDarkTheme,
|
||||
useLoaderState,
|
||||
useMediaQuery,
|
||||
useOutsideAlerter,
|
||||
} from '../hooks';
|
||||
import userService from '../api/services/userService';
|
||||
import ArrowLeft from '../assets/arrow-left.svg';
|
||||
import NoFilesIcon from '../assets/no-files.svg';
|
||||
import NoFilesDarkIcon from '../assets/no-files-dark.svg';
|
||||
import OutlineSource from '../assets/outline-source.svg';
|
||||
import SkeletonLoader from './SkeletonLoader';
|
||||
import ConfirmationModal from '../modals/ConfirmationModal';
|
||||
import { ActiveState } from '../models/misc';
|
||||
@@ -33,7 +37,7 @@ const LineNumberedTextarea: React.FC<LineNumberedTextareaProps> = ({
|
||||
ariaLabel,
|
||||
className = '',
|
||||
editable = true,
|
||||
onDoubleClick
|
||||
onDoubleClick,
|
||||
}) => {
|
||||
const { isMobile } = useMediaQuery();
|
||||
|
||||
@@ -45,28 +49,31 @@ const LineNumberedTextarea: React.FC<LineNumberedTextareaProps> = ({
|
||||
const contentLines = value.split('\n').length;
|
||||
|
||||
const heightOffset = isMobile ? 200 : 300;
|
||||
const minLinesForDisplay = Math.ceil((typeof window !== 'undefined' ? window.innerHeight - heightOffset : 600) / lineHeight);
|
||||
const minLinesForDisplay = Math.ceil(
|
||||
(typeof window !== 'undefined' ? window.innerHeight - heightOffset : 600) /
|
||||
lineHeight,
|
||||
);
|
||||
const totalLines = Math.max(contentLines, minLinesForDisplay);
|
||||
|
||||
return (
|
||||
<div className={`relative w-full ${className}`}>
|
||||
<div
|
||||
className="absolute left-0 top-0 w-8 lg:w-12 text-right text-gray-500 dark:text-gray-400 text-xs lg:text-sm font-mono leading-[19.93px] select-none pr-2 lg:pr-3 pointer-events-none"
|
||||
className="pointer-events-none absolute top-0 left-0 w-8 pr-2 text-right font-mono text-xs leading-[19.93px] text-gray-500 select-none lg:w-12 lg:pr-3 lg:text-sm dark:text-gray-400"
|
||||
style={{
|
||||
height: `${totalLines * lineHeight}px`
|
||||
height: `${totalLines * lineHeight}px`,
|
||||
}}
|
||||
>
|
||||
{Array.from({ length: totalLines }, (_, i) => (
|
||||
<div
|
||||
key={i + 1}
|
||||
className="flex items-center justify-end h-[19.93px] leading-[19.93px]"
|
||||
className="flex h-[19.93px] items-center justify-end leading-[19.93px]"
|
||||
>
|
||||
{i + 1}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<textarea
|
||||
className={`w-full resize-none bg-transparent dark:text-white font-['Inter'] text-[13.68px] leading-[19.93px] text-[#18181B] outline-none border-none pl-8 lg:pl-12 overflow-hidden ${isMobile ? 'min-h-[calc(100vh-200px)]' : 'min-h-[calc(100vh-300px)]'} ${!editable ? 'select-none' : ''}`}
|
||||
className={`w-full resize-none overflow-hidden border-none bg-transparent pl-8 font-['Inter'] text-[13.68px] leading-[19.93px] text-[#18181B] outline-none lg:pl-12 dark:text-white ${isMobile ? 'min-h-[calc(100vh-200px)]' : 'min-h-[calc(100vh-300px)]'} ${!editable ? 'select-none' : ''}`}
|
||||
value={value}
|
||||
onChange={editable ? handleChange : undefined}
|
||||
onDoubleClick={onDoubleClick}
|
||||
@@ -75,7 +82,7 @@ const LineNumberedTextarea: React.FC<LineNumberedTextareaProps> = ({
|
||||
rows={totalLines}
|
||||
readOnly={!editable}
|
||||
style={{
|
||||
height: `${totalLines * lineHeight}px`
|
||||
height: `${totalLines * lineHeight}px`,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
@@ -105,7 +112,9 @@ const Chunks: React.FC<ChunksProps> = ({
|
||||
onFileSelect,
|
||||
}) => {
|
||||
const [fileSearchQuery, setFileSearchQuery] = useState('');
|
||||
const [fileSearchResults, setFileSearchResults] = useState<SearchResult[]>([]);
|
||||
const [fileSearchResults, setFileSearchResults] = useState<SearchResult[]>(
|
||||
[],
|
||||
);
|
||||
const searchDropdownRef = useRef<HTMLDivElement>(null);
|
||||
const { t } = useTranslation();
|
||||
const token = useSelector(selectToken);
|
||||
@@ -120,7 +129,8 @@ const Chunks: React.FC<ChunksProps> = ({
|
||||
const [editingTitle, setEditingTitle] = useState('');
|
||||
const [editingText, setEditingText] = useState('');
|
||||
const [isAddingChunk, setIsAddingChunk] = useState(false);
|
||||
const [deleteModalState, setDeleteModalState] = useState<ActiveState>('INACTIVE');
|
||||
const [deleteModalState, setDeleteModalState] =
|
||||
useState<ActiveState>('INACTIVE');
|
||||
const [chunkToDelete, setChunkToDelete] = useState<ChunkType | null>(null);
|
||||
const [isEditing, setIsEditing] = useState(false);
|
||||
|
||||
@@ -189,7 +199,6 @@ const Chunks: React.FC<ChunksProps> = ({
|
||||
};
|
||||
|
||||
const handleUpdateChunk = (title: string, text: string, chunk: ChunkType) => {
|
||||
|
||||
if (!text.trim()) {
|
||||
return;
|
||||
}
|
||||
@@ -274,7 +283,7 @@ const Chunks: React.FC<ChunksProps> = ({
|
||||
useEffect(() => {
|
||||
!loading && fetchChunks();
|
||||
}, [page, perPage, path]);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
setSearchTerm('');
|
||||
setPage(1);
|
||||
@@ -284,35 +293,45 @@ const Chunks: React.FC<ChunksProps> = ({
|
||||
|
||||
const renderPathNavigation = () => {
|
||||
return (
|
||||
<div className="mb-0 min-h-[38px] flex flex-col sm:flex-row sm:items-center sm:justify-between text-base gap-2">
|
||||
<div className="mb-0 flex min-h-[38px] flex-col gap-2 text-base sm:flex-row sm:items-center sm:justify-between">
|
||||
<div className="flex w-full items-center sm:w-auto">
|
||||
<button
|
||||
className="mr-3 flex h-[29px] w-[29px] items-center justify-center rounded-full border p-2 text-sm text-gray-400 dark:border-0 dark:bg-[#28292D] dark:text-gray-500 dark:hover:bg-[#2E2F34] transition-all duration-200 font-medium"
|
||||
onClick={editingChunk ? () => setEditingChunk(null) : isAddingChunk ? () => setIsAddingChunk(false) : handleGoBack}
|
||||
className="mr-3 flex h-[29px] w-[29px] items-center justify-center rounded-full border p-2 text-sm font-medium text-gray-400 transition-all duration-200 dark:border-0 dark:bg-[#28292D] dark:text-gray-500 dark:hover:bg-[#2E2F34]"
|
||||
onClick={
|
||||
editingChunk
|
||||
? () => setEditingChunk(null)
|
||||
: isAddingChunk
|
||||
? () => setIsAddingChunk(false)
|
||||
: handleGoBack
|
||||
}
|
||||
>
|
||||
<img src={ArrowLeft} alt="left-arrow" className="h-3 w-3" />
|
||||
</button>
|
||||
|
||||
<div className="flex items-center flex-wrap">
|
||||
<div className="flex flex-wrap items-center">
|
||||
{/* Removed the directory icon */}
|
||||
<span className="text-[#7D54D1] font-semibold break-words">
|
||||
<span className="font-semibold break-words text-[#7D54D1]">
|
||||
{documentName}
|
||||
</span>
|
||||
|
||||
{pathParts.length > 0 && (
|
||||
<>
|
||||
<span className="mx-1 text-gray-500 flex-shrink-0">/</span>
|
||||
<span className="mx-1 flex-shrink-0 text-gray-500">/</span>
|
||||
{pathParts.map((part, index) => (
|
||||
<React.Fragment key={index}>
|
||||
<span className={`break-words ${
|
||||
index < pathParts.length - 1
|
||||
? 'text-[#7D54D1] font-medium'
|
||||
: 'text-gray-700 dark:text-gray-300'
|
||||
}`}>
|
||||
<span
|
||||
className={`break-words ${
|
||||
index < pathParts.length - 1
|
||||
? 'font-medium text-[#7D54D1]'
|
||||
: 'text-gray-700 dark:text-gray-300'
|
||||
}`}
|
||||
>
|
||||
{part}
|
||||
</span>
|
||||
{index < pathParts.length - 1 && (
|
||||
<span className="mx-1 text-gray-500 flex-shrink-0">/</span>
|
||||
<span className="mx-1 flex-shrink-0 text-gray-500">
|
||||
/
|
||||
</span>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))}
|
||||
@@ -321,18 +340,18 @@ const Chunks: React.FC<ChunksProps> = ({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-row flex-nowrap items-center gap-2 w-full sm:w-auto justify-end mt-2 sm:mt-0 overflow-x-auto">
|
||||
<div className="mt-2 flex w-full flex-row flex-nowrap items-center justify-end gap-2 overflow-x-auto sm:mt-0 sm:w-auto">
|
||||
{editingChunk ? (
|
||||
!isEditing ? (
|
||||
<>
|
||||
<button
|
||||
className="bg-purple-30 hover:bg-violets-are-blue flex h-[38px] min-w-[108px] items-center justify-center rounded-full px-4 text-[14px] whitespace-nowrap text-white font-medium"
|
||||
className="bg-purple-30 hover:bg-violets-are-blue flex h-[38px] min-w-[108px] items-center justify-center rounded-full px-4 text-[14px] font-medium whitespace-nowrap text-white"
|
||||
onClick={() => setIsEditing(true)}
|
||||
>
|
||||
{t('modals.chunk.edit')}
|
||||
</button>
|
||||
<button
|
||||
className="rounded-full border border-solid border-red-500 px-4 py-1 text-[14px] text-nowrap text-red-500 hover:bg-red-500 hover:text-white h-[38px] min-w-[108px] flex items-center justify-center font-medium"
|
||||
className="flex h-[38px] min-w-[108px] items-center justify-center rounded-full border border-solid border-red-500 px-4 py-1 text-[14px] font-medium text-nowrap text-red-500 hover:bg-red-500 hover:text-white"
|
||||
onClick={() => {
|
||||
confirmDeleteChunk(editingChunk);
|
||||
}}
|
||||
@@ -346,28 +365,40 @@ const Chunks: React.FC<ChunksProps> = ({
|
||||
onClick={() => {
|
||||
setIsEditing(false);
|
||||
}}
|
||||
className="dark:text-light-gray cursor-pointer rounded-full px-4 py-1 text-sm font-medium hover:bg-gray-100 dark:bg-transparent dark:hover:bg-[#767183]/50 text-nowrap h-[38px] min-w-[108px] flex items-center justify-center"
|
||||
className="dark:text-light-gray flex h-[38px] min-w-[108px] cursor-pointer items-center justify-center rounded-full px-4 py-1 text-sm font-medium text-nowrap hover:bg-gray-100 dark:bg-transparent dark:hover:bg-[#767183]/50"
|
||||
>
|
||||
{t('modals.chunk.cancel')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
if (editingText.trim()) {
|
||||
const hasChanges = editingTitle !== (editingChunk?.metadata?.title || '') ||
|
||||
editingText !== (editingChunk?.text || '');
|
||||
const hasChanges =
|
||||
editingTitle !==
|
||||
(editingChunk?.metadata?.title || '') ||
|
||||
editingText !== (editingChunk?.text || '');
|
||||
|
||||
if (hasChanges) {
|
||||
handleUpdateChunk(editingTitle, editingText, editingChunk);
|
||||
handleUpdateChunk(
|
||||
editingTitle,
|
||||
editingText,
|
||||
editingChunk,
|
||||
);
|
||||
}
|
||||
setIsEditing(false);
|
||||
setEditingChunk(null);
|
||||
}
|
||||
}}
|
||||
disabled={!editingText.trim() || (editingTitle === (editingChunk?.metadata?.title || '') && editingText === (editingChunk?.text || ''))}
|
||||
className={`text-nowrap rounded-full px-4 py-1 text-[14px] text-white transition-all flex items-center justify-center h-[38px] min-w-[108px] font-medium ${
|
||||
editingText.trim() && (editingTitle !== (editingChunk?.metadata?.title || '') || editingText !== (editingChunk?.text || ''))
|
||||
disabled={
|
||||
!editingText.trim() ||
|
||||
(editingTitle === (editingChunk?.metadata?.title || '') &&
|
||||
editingText === (editingChunk?.text || ''))
|
||||
}
|
||||
className={`flex h-[38px] min-w-[108px] items-center justify-center rounded-full px-4 py-1 text-[14px] font-medium text-nowrap text-white transition-all ${
|
||||
editingText.trim() &&
|
||||
(editingTitle !== (editingChunk?.metadata?.title || '') ||
|
||||
editingText !== (editingChunk?.text || ''))
|
||||
? 'bg-purple-30 hover:bg-violets-are-blue cursor-pointer'
|
||||
: 'bg-gray-400 cursor-not-allowed'
|
||||
: 'cursor-not-allowed bg-gray-400'
|
||||
}`}
|
||||
>
|
||||
{t('modals.chunk.save')}
|
||||
@@ -378,7 +409,7 @@ const Chunks: React.FC<ChunksProps> = ({
|
||||
<>
|
||||
<button
|
||||
onClick={() => setIsAddingChunk(false)}
|
||||
className="dark:text-light-gray cursor-pointer rounded-full px-4 py-1 text-sm font-medium hover:bg-gray-100 dark:bg-transparent dark:hover:bg-[#767183]/50 text-nowrap h-[38px] min-w-[108px] flex items-center justify-center"
|
||||
className="dark:text-light-gray flex h-[38px] min-w-[108px] cursor-pointer items-center justify-center rounded-full px-4 py-1 text-sm font-medium text-nowrap hover:bg-gray-100 dark:bg-transparent dark:hover:bg-[#767183]/50"
|
||||
>
|
||||
{t('modals.chunk.cancel')}
|
||||
</button>
|
||||
@@ -390,10 +421,10 @@ const Chunks: React.FC<ChunksProps> = ({
|
||||
}
|
||||
}}
|
||||
disabled={!editingText.trim()}
|
||||
className={`text-nowrap rounded-full px-4 py-1 text-[14px] text-white transition-all flex items-center justify-center h-[38px] min-w-[108px] font-medium ${
|
||||
className={`flex h-[38px] min-w-[108px] items-center justify-center rounded-full px-4 py-1 text-[14px] font-medium text-nowrap text-white transition-all ${
|
||||
editingText.trim()
|
||||
? 'bg-purple-30 hover:bg-violets-are-blue cursor-pointer'
|
||||
: 'bg-gray-400 cursor-not-allowed'
|
||||
: 'cursor-not-allowed bg-gray-400'
|
||||
}`}
|
||||
>
|
||||
{t('modals.chunk.add')}
|
||||
@@ -437,33 +468,30 @@ const Chunks: React.FC<ChunksProps> = ({
|
||||
setFileSearchResults([]);
|
||||
},
|
||||
[], // No additional dependencies
|
||||
false // Don't handle escape key
|
||||
false, // Don't handle escape key
|
||||
);
|
||||
|
||||
const renderFileSearch = () => {
|
||||
return (
|
||||
<div className="relative" ref={searchDropdownRef}>
|
||||
<div className="relative flex items-center">
|
||||
<div className="absolute left-3 pointer-events-none">
|
||||
<img src={SearchIcon} alt="Search" className="w-4 h-4" />
|
||||
<div className="pointer-events-none absolute left-3">
|
||||
<img src={SearchIcon} alt="Search" className="h-4 w-4" />
|
||||
</div>
|
||||
<input
|
||||
type="text"
|
||||
value={fileSearchQuery}
|
||||
onChange={(e) => handleFileSearchChange(e.target.value)}
|
||||
placeholder={t('settings.sources.searchFiles')}
|
||||
className={`w-full h-[38px] border border-[#D1D9E0] pl-10 pr-4 py-2 dark:border-[#6A6A6A]
|
||||
${fileSearchQuery
|
||||
? 'rounded-t-[6px]'
|
||||
: 'rounded-[6px]'
|
||||
}
|
||||
bg-transparent focus:outline-none dark:text-[#E0E0E0] transition-all duration-200`}
|
||||
className={`h-[38px] w-full border border-[#D1D9E0] py-2 pr-4 pl-10 dark:border-[#6A6A6A] ${
|
||||
fileSearchQuery ? 'rounded-t-[6px]' : 'rounded-[6px]'
|
||||
} bg-transparent transition-all duration-200 focus:outline-none dark:text-[#E0E0E0]`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{fileSearchQuery && (
|
||||
<div className="absolute z-10 max-h-[calc(100vh-200px)] w-full overflow-hidden rounded-b-[6px] border border-t-0 border-[#D1D9E0] bg-white shadow-lg dark:border-[#6A6A6A] dark:bg-[#1F2023]">
|
||||
<div className="max-h-[calc(100vh-200px)] overflow-y-auto overflow-x-hidden">
|
||||
<div className="max-h-[calc(100vh-200px)] overflow-x-hidden overflow-y-auto">
|
||||
{fileSearchResults.length === 0 ? (
|
||||
<div className="py-2 text-center text-sm text-gray-500 dark:text-gray-400">
|
||||
{t('settings.sources.noResults')}
|
||||
@@ -485,7 +513,7 @@ const Chunks: React.FC<ChunksProps> = ({
|
||||
alt={result.isFile ? 'File' : 'Folder'}
|
||||
className="mr-2 h-4 w-4 flex-shrink-0"
|
||||
/>
|
||||
<span className="text-sm dark:text-[#E0E0E0] truncate">
|
||||
<span className="truncate text-sm dark:text-[#E0E0E0]">
|
||||
{result.path.split('/').pop() || result.path}
|
||||
</span>
|
||||
</div>
|
||||
@@ -500,42 +528,39 @@ const Chunks: React.FC<ChunksProps> = ({
|
||||
|
||||
return (
|
||||
<div className="flex flex-col">
|
||||
<div className="mb-2">
|
||||
{renderPathNavigation()}
|
||||
</div>
|
||||
<div className="mb-2">{renderPathNavigation()}</div>
|
||||
<div className="flex gap-4">
|
||||
{onFileSearch && onFileSelect && (
|
||||
<div className="hidden lg:block w-[198px]">
|
||||
{renderFileSearch()}
|
||||
</div>
|
||||
<div className="hidden w-[198px] lg:block">{renderFileSearch()}</div>
|
||||
)}
|
||||
|
||||
{/* Right side: Chunks content */}
|
||||
<div className="flex-1">
|
||||
{!editingChunk && !isAddingChunk ? (
|
||||
<>
|
||||
<div className="mb-3 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
|
||||
<div className="flex-1 w-full flex items-center border border-[#D1D9E0] dark:border-[#6A6A6A] rounded-md overflow-hidden h-[38px]">
|
||||
<div className="px-4 flex items-center text-gray-700 dark:text-[#E0E0E0] font-medium whitespace-nowrap h-full">
|
||||
<div className="mb-3 flex flex-col items-start justify-between gap-3 sm:flex-row sm:items-center">
|
||||
<div className="flex h-[38px] w-full flex-1 items-center overflow-hidden rounded-md border border-[#D1D9E0] dark:border-[#6A6A6A]">
|
||||
<div className="flex h-full items-center px-4 font-medium whitespace-nowrap text-gray-700 dark:text-[#E0E0E0]">
|
||||
{totalChunks > 999999
|
||||
? `${(totalChunks / 1000000).toFixed(2)}M`
|
||||
: totalChunks > 999
|
||||
? `${(totalChunks / 1000).toFixed(2)}K`
|
||||
: totalChunks} {t('settings.sources.chunks')}
|
||||
: totalChunks}{' '}
|
||||
{t('settings.sources.chunks')}
|
||||
</div>
|
||||
<div className="h-full w-[1px] bg-[#D1D9E0] dark:bg-[#6A6A6A]"></div>
|
||||
<div className="flex-1 h-full">
|
||||
<div className="h-full flex-1">
|
||||
<input
|
||||
type="text"
|
||||
placeholder={t('settings.sources.searchPlaceholder')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full h-full px-3 py-2 bg-transparent border-none outline-none font-normal text-[13.56px] leading-[100%] dark:text-[#E0E0E0]"
|
||||
className="h-full w-full border-none bg-transparent px-3 py-2 text-[13.56px] leading-[100%] font-normal outline-none dark:text-[#E0E0E0]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="bg-purple-30 hover:bg-violets-are-blue flex h-[38px] w-full sm:w-auto min-w-[108px] items-center justify-center rounded-full px-4 text-[14px] whitespace-normal text-white shrink-0 font-medium"
|
||||
className="bg-purple-30 hover:bg-violets-are-blue flex h-[38px] w-full min-w-[108px] shrink-0 items-center justify-center rounded-full px-4 text-[14px] font-medium whitespace-normal text-white sm:w-auto"
|
||||
title={t('settings.sources.addChunk')}
|
||||
onClick={() => {
|
||||
setIsAddingChunk(true);
|
||||
@@ -547,13 +572,13 @@ const Chunks: React.FC<ChunksProps> = ({
|
||||
</button>
|
||||
</div>
|
||||
{loading ? (
|
||||
<div className="w-full grid grid-cols-1 sm:[grid-template-columns:repeat(auto-fit,minmax(400px,1fr))] gap-4 justify-items-start">
|
||||
<div className="grid w-full grid-cols-1 justify-items-start gap-4 sm:[grid-template-columns:repeat(auto-fit,minmax(400px,1fr))]">
|
||||
<SkeletonLoader component="chunkCards" count={perPage} />
|
||||
</div>
|
||||
) : (
|
||||
<div className="w-full grid grid-cols-1 sm:[grid-template-columns:repeat(auto-fit,minmax(400px,1fr))] gap-4 justify-items-start">
|
||||
<div className="grid w-full grid-cols-1 justify-items-start gap-4 sm:[grid-template-columns:repeat(auto-fit,minmax(400px,1fr))]">
|
||||
{filteredChunks.length === 0 ? (
|
||||
<div className="col-span-full w-full min-h-[50vh] flex flex-col items-center justify-center text-center text-gray-500 dark:text-gray-400">
|
||||
<div className="col-span-full flex min-h-[50vh] w-full flex-col items-center justify-center text-center text-gray-500 dark:text-gray-400">
|
||||
<img
|
||||
src={isDarkTheme ? NoFilesDarkIcon : NoFilesIcon}
|
||||
alt={t('settings.sources.noChunksAlt')}
|
||||
@@ -565,7 +590,7 @@ const Chunks: React.FC<ChunksProps> = ({
|
||||
filteredChunks.map((chunk, index) => (
|
||||
<div
|
||||
key={index}
|
||||
className="transform transition-transform duration-200 hover:scale-105 relative flex h-[197px] flex-col justify-between rounded-[5.86px] border border-[#D1D9E0] dark:border-[#6A6A6A] overflow-hidden cursor-pointer w-full max-w-[487px]"
|
||||
className="relative flex h-[197px] w-full max-w-[487px] transform cursor-pointer flex-col justify-between overflow-hidden rounded-[5.86px] border border-[#D1D9E0] transition-transform duration-200 hover:scale-105 dark:border-[#6A6A6A]"
|
||||
onClick={() => {
|
||||
setEditingChunk(chunk);
|
||||
setEditingTitle(chunk.metadata?.title || '');
|
||||
@@ -573,13 +598,16 @@ const Chunks: React.FC<ChunksProps> = ({
|
||||
}}
|
||||
>
|
||||
<div className="w-full">
|
||||
<div className="flex w-full items-center justify-between border-b border-[#D1D9E0] bg-[#F6F8FA] dark:bg-[#27282D] dark:border-[#6A6A6A] px-4 py-3">
|
||||
<div className="text-[#59636E] text-sm dark:text-[#E0E0E0]">
|
||||
{chunk.metadata.token_count ? chunk.metadata.token_count.toLocaleString() : '-'} {t('settings.sources.tokensUnit')}
|
||||
<div className="flex w-full items-center justify-between border-b border-[#D1D9E0] bg-[#F6F8FA] px-4 py-3 dark:border-[#6A6A6A] dark:bg-[#27282D]">
|
||||
<div className="text-sm text-[#59636E] dark:text-[#E0E0E0]">
|
||||
{chunk.metadata.token_count
|
||||
? chunk.metadata.token_count.toLocaleString()
|
||||
: '-'}{' '}
|
||||
{t('settings.sources.tokensUnit')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-4 pt-3 pb-6">
|
||||
<p className="font-['Inter'] text-[13.68px] leading-[19.93px] text-[#18181B] dark:text-[#E0E0E0] line-clamp-6 font-normal">
|
||||
<p className="line-clamp-6 font-['Inter'] text-[13.68px] leading-[19.93px] font-normal text-[#18181B] dark:text-[#E0E0E0]">
|
||||
{chunk.text}
|
||||
</p>
|
||||
</div>
|
||||
@@ -592,7 +620,7 @@ const Chunks: React.FC<ChunksProps> = ({
|
||||
</>
|
||||
) : isAddingChunk ? (
|
||||
<div className="w-full">
|
||||
<div className="relative border border-[#D1D9E0] dark:border-[#6A6A6A] rounded-lg overflow-hidden">
|
||||
<div className="relative overflow-hidden rounded-lg border border-[#D1D9E0] dark:border-[#6A6A6A]">
|
||||
<LineNumberedTextarea
|
||||
value={editingText}
|
||||
onChange={setEditingText}
|
||||
@@ -601,45 +629,53 @@ const Chunks: React.FC<ChunksProps> = ({
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : editingChunk && (
|
||||
<div className="w-full">
|
||||
<div className="relative flex flex-col rounded-[5.86px] border border-[#D1D9E0] dark:border-[#6A6A6A] overflow-hidden w-full">
|
||||
<div className="flex w-full items-center justify-between border-b border-[#D1D9E0] bg-[#F6F8FA] dark:bg-[#27282D] dark:border-[#6A6A6A] px-4 py-3">
|
||||
<div className="text-[#59636E] text-sm dark:text-[#E0E0E0]">
|
||||
{editingChunk.metadata.token_count ? editingChunk.metadata.token_count.toLocaleString() : '-'} {t('settings.sources.tokensUnit')}
|
||||
) : (
|
||||
editingChunk && (
|
||||
<div className="w-full">
|
||||
<div className="relative flex w-full flex-col overflow-hidden rounded-[5.86px] border border-[#D1D9E0] dark:border-[#6A6A6A]">
|
||||
<div className="flex w-full items-center justify-between border-b border-[#D1D9E0] bg-[#F6F8FA] px-4 py-3 dark:border-[#6A6A6A] dark:bg-[#27282D]">
|
||||
<div className="text-sm text-[#59636E] dark:text-[#E0E0E0]">
|
||||
{editingChunk.metadata.token_count
|
||||
? editingChunk.metadata.token_count.toLocaleString()
|
||||
: '-'}{' '}
|
||||
{t('settings.sources.tokensUnit')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="overflow-hidden p-4">
|
||||
<LineNumberedTextarea
|
||||
value={isEditing ? editingText : editingChunk.text}
|
||||
onChange={setEditingText}
|
||||
ariaLabel={t('modals.chunk.promptText')}
|
||||
editable={isEditing}
|
||||
onDoubleClick={() => {
|
||||
if (!isEditing) {
|
||||
setIsEditing(true);
|
||||
setEditingTitle(editingChunk.metadata.title || '');
|
||||
setEditingText(editingChunk.text);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-4 overflow-hidden">
|
||||
<LineNumberedTextarea
|
||||
value={isEditing ? editingText : editingChunk.text}
|
||||
onChange={setEditingText}
|
||||
ariaLabel={t('modals.chunk.promptText')}
|
||||
editable={isEditing}
|
||||
onDoubleClick={() => {
|
||||
if (!isEditing) {
|
||||
setIsEditing(true);
|
||||
setEditingTitle(editingChunk.metadata.title || '');
|
||||
setEditingText(editingChunk.text);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
)}
|
||||
|
||||
{!loading && totalChunks > perPage && !editingChunk && !isAddingChunk && (
|
||||
<Pagination
|
||||
currentPage={page}
|
||||
totalPages={Math.ceil(totalChunks / perPage)}
|
||||
rowsPerPage={perPage}
|
||||
onPageChange={setPage}
|
||||
onRowsPerPageChange={(rows) => {
|
||||
setPerPage(rows);
|
||||
setPage(1);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{!loading &&
|
||||
totalChunks > perPage &&
|
||||
!editingChunk &&
|
||||
!isAddingChunk && (
|
||||
<Pagination
|
||||
currentPage={page}
|
||||
totalPages={Math.ceil(totalChunks / perPage)}
|
||||
rowsPerPage={perPage}
|
||||
onPageChange={setPage}
|
||||
onRowsPerPageChange={(rows) => {
|
||||
setPerPage(rows);
|
||||
setPage(1);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -111,15 +111,27 @@ const ConnectorAuth: React.FC<ConnectorAuthProps> = ({
|
||||
return (
|
||||
<>
|
||||
{errorMessage && (
|
||||
<div className="mb-4 flex items-center gap-2 rounded-lg border border-[#E60000] dark:border-[#D42626] bg-transparent dark:bg-[#D426261A] p-2">
|
||||
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M7.09974 24.5422H22.9C24.5156 24.5422 25.5228 22.7901 24.715 21.3947L16.8149 7.74526C16.007 6.34989 13.9927 6.34989 13.1848 7.74526L5.28471 21.3947C4.47686 22.7901 5.48405 24.5422 7.09974 24.5422ZM14.9998 17.1981C14.4228 17.1981 13.9507 16.726 13.9507 16.149V14.0507C13.9507 13.4736 14.4228 13.0015 14.9998 13.0015C15.5769 13.0015 16.049 13.4736 16.049 14.0507V16.149C16.049 16.726 15.5769 17.1981 14.9998 17.1981ZM16.049 21.3947H13.9507V19.2964H16.049V21.3947Z" fill={isDarkTheme ? '#EECF56' : '#E60000'} />
|
||||
<div className="mb-4 flex items-center gap-2 rounded-lg border border-[#E60000] bg-transparent p-2 dark:border-[#D42626] dark:bg-[#D426261A]">
|
||||
<svg
|
||||
width="30"
|
||||
height="30"
|
||||
viewBox="0 0 30 30"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M7.09974 24.5422H22.9C24.5156 24.5422 25.5228 22.7901 24.715 21.3947L16.8149 7.74526C16.007 6.34989 13.9927 6.34989 13.1848 7.74526L5.28471 21.3947C4.47686 22.7901 5.48405 24.5422 7.09974 24.5422ZM14.9998 17.1981C14.4228 17.1981 13.9507 16.726 13.9507 16.149V14.0507C13.9507 13.4736 14.4228 13.0015 14.9998 13.0015C15.5769 13.0015 16.049 13.4736 16.049 14.0507V16.149C16.049 16.726 15.5769 17.1981 14.9998 17.1981ZM16.049 21.3947H13.9507V19.2964H16.049V21.3947Z"
|
||||
fill={isDarkTheme ? '#EECF56' : '#E60000'}
|
||||
/>
|
||||
</svg>
|
||||
|
||||
<span className='text-[#E60000] dark:text-[#E37064] text-sm' style={{
|
||||
fontFamily: 'Inter',
|
||||
lineHeight: '100%'
|
||||
}}>
|
||||
<span
|
||||
className="text-sm text-[#E60000] dark:text-[#E37064]"
|
||||
style={{
|
||||
fontFamily: 'Inter',
|
||||
lineHeight: '100%',
|
||||
}}
|
||||
>
|
||||
{errorMessage}
|
||||
</span>
|
||||
</div>
|
||||
@@ -127,17 +139,20 @@ const ConnectorAuth: React.FC<ConnectorAuthProps> = ({
|
||||
|
||||
{isConnected ? (
|
||||
<div className="mb-4">
|
||||
<div className="w-full flex items-center justify-between rounded-[10px] bg-[#8FDD51] px-4 py-2 text-[#212121] font-medium text-sm">
|
||||
<div className="flex w-full items-center justify-between rounded-[10px] bg-[#8FDD51] px-4 py-2 text-sm font-medium text-[#212121]">
|
||||
<div className="flex items-center gap-2">
|
||||
<svg className="h-4 w-4" viewBox="0 0 24 24">
|
||||
<path fill="currentColor" d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z" />
|
||||
<path
|
||||
fill="currentColor"
|
||||
d="M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41z"
|
||||
/>
|
||||
</svg>
|
||||
<span>Connected as {userEmail}</span>
|
||||
</div>
|
||||
{onDisconnect && (
|
||||
<button
|
||||
onClick={onDisconnect}
|
||||
className="text-[#212121] hover:text-gray-700 font-medium text-xs underline"
|
||||
className="text-xs font-medium text-[#212121] underline hover:text-gray-700"
|
||||
>
|
||||
Disconnect
|
||||
</button>
|
||||
@@ -162,4 +177,4 @@ const ConnectorAuth: React.FC<ConnectorAuthProps> = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default ConnectorAuth;
|
||||
export default ConnectorAuth;
|
||||
|
||||
@@ -76,7 +76,8 @@ const ConnectorTreeComponent: React.FC<ConnectorTreeComponentProps> = ({
|
||||
const [syncProgress, setSyncProgress] = useState<number>(0);
|
||||
const [sourceProvider, setSourceProvider] = useState<string>('');
|
||||
const [syncDone, setSyncDone] = useState<boolean>(false);
|
||||
const [syncConfirmationModal, setSyncConfirmationModal] = useState<ActiveState>('INACTIVE');
|
||||
const [syncConfirmationModal, setSyncConfirmationModal] =
|
||||
useState<ActiveState>('INACTIVE');
|
||||
|
||||
useOutsideAlerter(
|
||||
searchDropdownRef,
|
||||
@@ -392,31 +393,26 @@ const ConnectorTreeComponent: React.FC<ConnectorTreeComponentProps> = ({
|
||||
const parentRow =
|
||||
currentPath.length > 0
|
||||
? [
|
||||
<TableRow
|
||||
key="parent-dir"
|
||||
onClick={navigateUp}
|
||||
>
|
||||
<TableCell width="40%" align="left">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
src={FolderIcon}
|
||||
alt={t('settings.sources.parentFolderAlt')}
|
||||
className="mr-2 h-4 w-4 flex-shrink-0"
|
||||
/>
|
||||
<span className="truncate">
|
||||
..
|
||||
</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell width="30%" align="left">
|
||||
-
|
||||
</TableCell>
|
||||
<TableCell width="20%" align="left">
|
||||
-
|
||||
</TableCell>
|
||||
<TableCell width="10%" align="right"></TableCell>
|
||||
</TableRow>,
|
||||
]
|
||||
<TableRow key="parent-dir" onClick={navigateUp}>
|
||||
<TableCell width="40%" align="left">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
src={FolderIcon}
|
||||
alt={t('settings.sources.parentFolderAlt')}
|
||||
className="mr-2 h-4 w-4 flex-shrink-0"
|
||||
/>
|
||||
<span className="truncate">..</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell width="30%" align="left">
|
||||
-
|
||||
</TableCell>
|
||||
<TableCell width="20%" align="left">
|
||||
-
|
||||
</TableCell>
|
||||
<TableCell width="10%" align="right"></TableCell>
|
||||
</TableRow>,
|
||||
]
|
||||
: [];
|
||||
|
||||
// Sort entries: directories first, then files, both alphabetically
|
||||
@@ -444,10 +440,7 @@ const ConnectorTreeComponent: React.FC<ConnectorTreeComponentProps> = ({
|
||||
const dirStats = calculateDirectoryStats(node as DirectoryStructure);
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
key={itemId}
|
||||
onClick={() => navigateToDirectory(name)}
|
||||
>
|
||||
<TableRow key={itemId} onClick={() => navigateToDirectory(name)}>
|
||||
<TableCell width="40%" align="left">
|
||||
<div className="flex min-w-0 items-center">
|
||||
<img
|
||||
@@ -455,9 +448,7 @@ const ConnectorTreeComponent: React.FC<ConnectorTreeComponentProps> = ({
|
||||
alt={t('settings.sources.folderAlt')}
|
||||
className="mr-2 h-4 w-4 flex-shrink-0"
|
||||
/>
|
||||
<span className="truncate">
|
||||
{name}
|
||||
</span>
|
||||
<span className="truncate">{name}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell width="30%" align="left">
|
||||
@@ -472,7 +463,7 @@ const ConnectorTreeComponent: React.FC<ConnectorTreeComponentProps> = ({
|
||||
<div ref={menuRef} className="relative">
|
||||
<button
|
||||
onClick={(e) => handleMenuClick(e, itemId)}
|
||||
className="inline-flex h-[35px] w-[24px] shrink-0 items-center justify-center rounded-md transition-colors hover:bg-[#EBEBEB] dark:hover:bg-[#26272E] font-medium"
|
||||
className="inline-flex h-[35px] w-[24px] shrink-0 items-center justify-center rounded-md font-medium transition-colors hover:bg-[#EBEBEB] dark:hover:bg-[#26272E]"
|
||||
aria-label={t('settings.sources.menuAlt')}
|
||||
>
|
||||
<img
|
||||
@@ -505,10 +496,7 @@ const ConnectorTreeComponent: React.FC<ConnectorTreeComponentProps> = ({
|
||||
const menuRef = getMenuRef(itemId);
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
key={itemId}
|
||||
onClick={() => handleFileClick(name)}
|
||||
>
|
||||
<TableRow key={itemId} onClick={() => handleFileClick(name)}>
|
||||
<TableCell width="40%" align="left">
|
||||
<div className="flex min-w-0 items-center">
|
||||
<img
|
||||
@@ -516,9 +504,7 @@ const ConnectorTreeComponent: React.FC<ConnectorTreeComponentProps> = ({
|
||||
alt={t('settings.sources.fileAlt')}
|
||||
className="mr-2 h-4 w-4 flex-shrink-0"
|
||||
/>
|
||||
<span className="truncate">
|
||||
{name}
|
||||
</span>
|
||||
<span className="truncate">{name}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell width="30%" align="left">
|
||||
@@ -730,9 +716,7 @@ const ConnectorTreeComponent: React.FC<ConnectorTreeComponentProps> = ({
|
||||
</TableHeader>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{renderFileTree(getCurrentDirectory())}
|
||||
</TableBody>
|
||||
<TableBody>{renderFileTree(getCurrentDirectory())}</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</div>
|
||||
|
||||
@@ -61,12 +61,12 @@ const Pagination: React.FC<PaginationProps> = ({
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={toggleDropdown}
|
||||
className="rounded border px-3 py-1 hover:bg-gray-200 dark:bg-dark-charcoal dark:text-light-gray dark:hover:bg-neutral-700"
|
||||
className="dark:bg-dark-charcoal dark:text-light-gray rounded border px-3 py-1 hover:bg-gray-200 dark:hover:bg-neutral-700"
|
||||
>
|
||||
{rowsPerPage}
|
||||
</button>
|
||||
<div
|
||||
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 ${
|
||||
className={`ring-opacity-5 dark:bg-dark-charcoal absolute right-0 z-50 mt-1 w-28 transform bg-white shadow-lg ring-1 ring-black transition-all duration-200 ease-in-out ${
|
||||
isDropdownOpen
|
||||
? 'block scale-100 opacity-100'
|
||||
: 'hidden scale-95 opacity-0'
|
||||
@@ -78,8 +78,8 @@ const Pagination: React.FC<PaginationProps> = ({
|
||||
onClick={() => handleSelectRowsPerPage(option)}
|
||||
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'
|
||||
? 'dark:text-light-gray bg-gray-100 dark:bg-neutral-700'
|
||||
: 'dark:bg-dark-charcoal dark:text-light-gray bg-white'
|
||||
}`}
|
||||
>
|
||||
{option}
|
||||
|
||||
@@ -88,7 +88,7 @@ export default function DropdownMenu({
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<div
|
||||
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}`}
|
||||
className={`ring-opacity-5 dark:bg-dark-charcoal w-28 transform rounded-md bg-white shadow-lg ring-1 ring-black transition-all duration-200 ease-in-out ${className}`}
|
||||
>
|
||||
<div
|
||||
role="menu"
|
||||
@@ -99,10 +99,10 @@ export default function DropdownMenu({
|
||||
{options.map((option, idx) => (
|
||||
<div
|
||||
id={`option-${idx}`}
|
||||
className={`cursor-pointer px-4 py-2 text-xs hover:bg-gray-100 dark:text-light-gray dark:hover:bg-purple-taupe ${
|
||||
className={`dark:text-light-gray dark:hover:bg-purple-taupe cursor-pointer px-4 py-2 text-xs hover:bg-gray-100 ${
|
||||
selectedOption.value === option.value
|
||||
? 'bg-gray-100 dark:bg-purple-taupe'
|
||||
: 'bg-white dark:bg-dark-charcoal'
|
||||
? 'dark:bg-purple-taupe bg-gray-100'
|
||||
: 'dark:bg-dark-charcoal bg-white'
|
||||
}`}
|
||||
role="menuitem"
|
||||
key={option.value}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import React, { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { formatBytes } from '../utils/stringUtils';
|
||||
import { formatDate } from '../utils/dateTimeUtils';
|
||||
import { getSessionToken, setSessionToken, removeSessionToken } from '../utils/providerUtils';
|
||||
import {
|
||||
getSessionToken,
|
||||
setSessionToken,
|
||||
removeSessionToken,
|
||||
} from '../utils/providerUtils';
|
||||
import ConnectorAuth from '../components/ConnectorAuth';
|
||||
import FileIcon from '../assets/file.svg';
|
||||
import FolderIcon from '../assets/folder.svg';
|
||||
@@ -28,7 +32,10 @@ interface CloudFile {
|
||||
}
|
||||
|
||||
interface CloudFilePickerProps {
|
||||
onSelectionChange: (selectedFileIds: string[], selectedFolderIds?: string[]) => void;
|
||||
onSelectionChange: (
|
||||
selectedFileIds: string[],
|
||||
selectedFolderIds?: string[],
|
||||
) => void;
|
||||
onDisconnect?: () => void;
|
||||
provider: string;
|
||||
token: string | null;
|
||||
@@ -51,23 +58,30 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
} as const;
|
||||
|
||||
const getProviderConfig = (provider: string) => {
|
||||
return PROVIDER_CONFIG[provider as keyof typeof PROVIDER_CONFIG] || {
|
||||
displayName: provider,
|
||||
rootName: 'Root',
|
||||
};
|
||||
return (
|
||||
PROVIDER_CONFIG[provider as keyof typeof PROVIDER_CONFIG] || {
|
||||
displayName: provider,
|
||||
rootName: 'Root',
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const [files, setFiles] = useState<CloudFile[]>([]);
|
||||
const [selectedFiles, setSelectedFiles] = useState<string[]>(initialSelectedFiles);
|
||||
const [selectedFiles, setSelectedFiles] =
|
||||
useState<string[]>(initialSelectedFiles);
|
||||
const [selectedFolders, setSelectedFolders] = useState<string[]>([]);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [hasMoreFiles, setHasMoreFiles] = useState(false);
|
||||
const [nextPageToken, setNextPageToken] = useState<string | null>(null);
|
||||
const [currentFolderId, setCurrentFolderId] = useState<string | null>(null);
|
||||
const [folderPath, setFolderPath] = useState<Array<{ id: string | null, name: string }>>([{
|
||||
id: null,
|
||||
name: getProviderConfig(provider).rootName
|
||||
}]);
|
||||
const [folderPath, setFolderPath] = useState<
|
||||
Array<{ id: string | null; name: string }>
|
||||
>([
|
||||
{
|
||||
id: null,
|
||||
name: getProviderConfig(provider).rootName,
|
||||
},
|
||||
]);
|
||||
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||
const [authError, setAuthError] = useState<string>('');
|
||||
const [isConnected, setIsConnected] = useState(false);
|
||||
@@ -77,9 +91,11 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
const searchTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
|
||||
const isFolder = (file: CloudFile) => {
|
||||
return file.isFolder ||
|
||||
return (
|
||||
file.isFolder ||
|
||||
file.type === 'application/vnd.google-apps.folder' ||
|
||||
file.type === 'folder';
|
||||
file.type === 'folder'
|
||||
);
|
||||
};
|
||||
|
||||
const loadCloudFiles = useCallback(
|
||||
@@ -87,7 +103,7 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
sessionToken: string,
|
||||
folderId: string | null,
|
||||
pageToken?: string,
|
||||
searchQuery: string = ''
|
||||
searchQuery = '',
|
||||
) => {
|
||||
setIsLoading(true);
|
||||
|
||||
@@ -101,7 +117,7 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
provider: provider,
|
||||
@@ -109,13 +125,15 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
folder_id: folderId,
|
||||
limit: 10,
|
||||
page_token: pageToken,
|
||||
search_query: searchQuery
|
||||
})
|
||||
search_query: searchQuery,
|
||||
}),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
setFiles(prev => pageToken ? [...prev, ...data.files] : data.files);
|
||||
setFiles((prev) =>
|
||||
pageToken ? [...prev, ...data.files] : data.files,
|
||||
);
|
||||
setNextPageToken(data.next_page_token);
|
||||
setHasMoreFiles(!!data.next_page_token);
|
||||
} else {
|
||||
@@ -133,7 +151,7 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[token, provider]
|
||||
[token, provider],
|
||||
);
|
||||
|
||||
const validateAndLoadFiles = useCallback(async () => {
|
||||
@@ -145,14 +163,20 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
|
||||
try {
|
||||
const apiHost = import.meta.env.VITE_API_HOST;
|
||||
const validateResponse = await fetch(`${apiHost}/api/connectors/validate-session`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
const validateResponse = await fetch(
|
||||
`${apiHost}/api/connectors/validate-session`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
provider: provider,
|
||||
session_token: sessionToken,
|
||||
}),
|
||||
},
|
||||
body: JSON.stringify({ provider: provider, session_token: sessionToken })
|
||||
});
|
||||
);
|
||||
|
||||
if (!validateResponse.ok) {
|
||||
removeSessionToken(provider);
|
||||
@@ -171,14 +195,20 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
setNextPageToken(null);
|
||||
setHasMoreFiles(false);
|
||||
setCurrentFolderId(null);
|
||||
setFolderPath([{
|
||||
id: null, name: getProviderConfig(provider).rootName
|
||||
}]);
|
||||
setFolderPath([
|
||||
{
|
||||
id: null,
|
||||
name: getProviderConfig(provider).rootName,
|
||||
},
|
||||
]);
|
||||
loadCloudFiles(sessionToken, null, undefined, '');
|
||||
} else {
|
||||
removeSessionToken(provider);
|
||||
setIsConnected(false);
|
||||
setAuthError(validateData.error || 'Session expired. Please reconnect your account.');
|
||||
setAuthError(
|
||||
validateData.error ||
|
||||
'Session expired. Please reconnect your account.',
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error validating session:', error);
|
||||
@@ -201,10 +231,23 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
if (isNearBottom && hasMoreFiles && !isLoading && nextPageToken) {
|
||||
const sessionToken = getSessionToken(provider);
|
||||
if (sessionToken) {
|
||||
loadCloudFiles(sessionToken, currentFolderId, nextPageToken, searchQuery);
|
||||
loadCloudFiles(
|
||||
sessionToken,
|
||||
currentFolderId,
|
||||
nextPageToken,
|
||||
searchQuery,
|
||||
);
|
||||
}
|
||||
}
|
||||
}, [hasMoreFiles, isLoading, nextPageToken, currentFolderId, searchQuery, provider, loadCloudFiles]);
|
||||
}, [
|
||||
hasMoreFiles,
|
||||
isLoading,
|
||||
nextPageToken,
|
||||
currentFolderId,
|
||||
searchQuery,
|
||||
provider,
|
||||
loadCloudFiles,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const scrollContainer = scrollContainerRef.current;
|
||||
@@ -245,7 +288,7 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
setIsLoading(true);
|
||||
|
||||
setCurrentFolderId(folderId);
|
||||
setFolderPath(prev => [...prev, { id: folderId, name: folderName }]);
|
||||
setFolderPath((prev) => [...prev, { id: folderId, name: folderName }]);
|
||||
setSearchQuery('');
|
||||
|
||||
const sessionToken = getSessionToken(provider);
|
||||
@@ -273,13 +316,13 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
const handleFileSelect = (fileId: string, isFolder: boolean) => {
|
||||
if (isFolder) {
|
||||
const newSelectedFolders = selectedFolders.includes(fileId)
|
||||
? selectedFolders.filter(id => id !== fileId)
|
||||
? selectedFolders.filter((id) => id !== fileId)
|
||||
: [...selectedFolders, fileId];
|
||||
setSelectedFolders(newSelectedFolders);
|
||||
onSelectionChange(selectedFiles, newSelectedFolders);
|
||||
} else {
|
||||
const newSelectedFiles = selectedFiles.includes(fileId)
|
||||
? selectedFiles.filter(id => id !== fileId)
|
||||
? selectedFiles.filter((id) => id !== fileId)
|
||||
: [...selectedFiles, fileId];
|
||||
setSelectedFiles(newSelectedFiles);
|
||||
onSelectionChange(newSelectedFiles, selectedFolders);
|
||||
@@ -287,11 +330,11 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<div className=''>
|
||||
<div className="">
|
||||
{authError && (
|
||||
<div className="text-red-500 text-sm mb-4 text-center">{authError}</div>
|
||||
<div className="mb-4 text-center text-sm text-red-500">{authError}</div>
|
||||
)}
|
||||
|
||||
|
||||
<ConnectorAuth
|
||||
provider={provider}
|
||||
onSuccess={(data) => {
|
||||
@@ -318,10 +361,18 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({ provider: provider, session_token: sessionToken })
|
||||
}).catch(err => console.error(`Error disconnecting from ${getProviderConfig(provider).displayName}:`, err));
|
||||
body: JSON.stringify({
|
||||
provider: provider,
|
||||
session_token: sessionToken,
|
||||
}),
|
||||
}).catch((err) =>
|
||||
console.error(
|
||||
`Error disconnecting from ${getProviderConfig(provider).displayName}:`,
|
||||
err,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
removeSessionToken(provider);
|
||||
@@ -337,13 +388,16 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
/>
|
||||
|
||||
{isConnected && (
|
||||
<div className="border border-[#D7D7D7] rounded-lg dark:border-[#6A6A6A] mt-3">
|
||||
<div className="border-[#EEE6FF78] dark:border-[#6A6A6A] rounded-t-lg">
|
||||
<div className="mt-3 rounded-lg border border-[#D7D7D7] dark:border-[#6A6A6A]">
|
||||
<div className="rounded-t-lg border-[#EEE6FF78] dark:border-[#6A6A6A]">
|
||||
{/* Breadcrumb navigation */}
|
||||
<div className="px-4 pt-4 bg-[#EEE6FF78] dark:bg-[#2A262E] rounded-t-lg">
|
||||
<div className="flex items-center gap-1 mb-2">
|
||||
<div className="rounded-t-lg bg-[#EEE6FF78] px-4 pt-4 dark:bg-[#2A262E]">
|
||||
<div className="mb-2 flex items-center gap-1">
|
||||
{folderPath.map((path, index) => (
|
||||
<div key={path.id || 'root'} className="flex items-center gap-1">
|
||||
<div
|
||||
key={path.id || 'root'}
|
||||
className="flex items-center gap-1"
|
||||
>
|
||||
{index > 0 && <span className="text-gray-400">/</span>}
|
||||
<button
|
||||
onClick={() => navigateBack(index)}
|
||||
@@ -369,7 +423,9 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
colorVariant="silver"
|
||||
borderVariant="thin"
|
||||
labelBgClassName="bg-[#EEE6FF78] dark:bg-[#2A262E]"
|
||||
leftIcon={<img src={SearchIcon} alt="Search" width={16} height={16} />}
|
||||
leftIcon={
|
||||
<img src={SearchIcon} alt="Search" width={16} height={16} />
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -386,7 +442,7 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
className="scrollbar-thin md:w-4xl lg:w-5xl"
|
||||
bordered={false}
|
||||
>
|
||||
{(
|
||||
{
|
||||
<>
|
||||
<Table minWidth="1200px">
|
||||
<TableHead>
|
||||
@@ -411,13 +467,16 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
>
|
||||
<TableCell width="40px" align="center">
|
||||
<div
|
||||
className="flex h-5 w-5 text-sm shrink-0 items-center justify-center border border-[#EEE6FF78] p-[0.5px] dark:border-[#6A6A6A] cursor-pointer mx-auto"
|
||||
className="mx-auto flex h-5 w-5 shrink-0 cursor-pointer items-center justify-center border border-[#EEE6FF78] p-[0.5px] text-sm dark:border-[#6A6A6A]"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleFileSelect(file.id, isFolder(file));
|
||||
}}
|
||||
>
|
||||
{(isFolder(file) ? selectedFolders : selectedFiles).includes(file.id) && (
|
||||
{(isFolder(file)
|
||||
? selectedFolders
|
||||
: selectedFiles
|
||||
).includes(file.id) && (
|
||||
<img
|
||||
src={CheckIcon}
|
||||
alt="Selected"
|
||||
@@ -427,21 +486,21 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<div className="flex items-center gap-3 min-w-0">
|
||||
<div className="flex min-w-0 items-center gap-3">
|
||||
<div className="flex-shrink-0">
|
||||
<img
|
||||
src={isFolder(file) ? FolderIcon : FileIcon}
|
||||
alt={isFolder(file) ? "Folder" : "File"}
|
||||
alt={isFolder(file) ? 'Folder' : 'File'}
|
||||
className="h-6 w-6"
|
||||
/>
|
||||
</div>
|
||||
<span className="truncate">{file.name}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell className='text-xs'>
|
||||
<TableCell className="text-xs">
|
||||
{formatDate(file.modifiedTime)}
|
||||
</TableCell>
|
||||
<TableCell className='text-xs'>
|
||||
<TableCell className="text-xs">
|
||||
{file.size ? formatBytes(file.size) : '-'}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
@@ -450,7 +509,7 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
</Table>
|
||||
|
||||
{isLoading && (
|
||||
<div className="flex items-center justify-center p-4 border-t border-[#EEE6FF78] dark:border-[#6A6A6A]">
|
||||
<div className="flex items-center justify-center border-t border-[#EEE6FF78] p-4 dark:border-[#6A6A6A]">
|
||||
<div className="inline-flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
<div className="h-4 w-4 animate-spin rounded-full border-2 border-blue-500 border-t-transparent"></div>
|
||||
Loading more files...
|
||||
@@ -458,7 +517,7 @@ export const FilePicker: React.FC<CloudFilePickerProps> = ({
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
}
|
||||
</TableContainer>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -542,31 +542,26 @@ const FileTreeComponent: React.FC<FileTreeComponentProps> = ({
|
||||
const parentRow =
|
||||
currentPath.length > 0
|
||||
? [
|
||||
<TableRow
|
||||
key="parent-dir"
|
||||
onClick={navigateUp}
|
||||
>
|
||||
<TableCell width="40%" align="left">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
src={FolderIcon}
|
||||
alt={t('settings.sources.parentFolderAlt')}
|
||||
className="mr-2 h-4 w-4 flex-shrink-0"
|
||||
/>
|
||||
<span className="truncate">
|
||||
..
|
||||
</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell width="30%" align="left">
|
||||
-
|
||||
</TableCell>
|
||||
<TableCell width="20%" align="right">
|
||||
-
|
||||
</TableCell>
|
||||
<TableCell width="10%" align="right"></TableCell>
|
||||
</TableRow>,
|
||||
]
|
||||
<TableRow key="parent-dir" onClick={navigateUp}>
|
||||
<TableCell width="40%" align="left">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
src={FolderIcon}
|
||||
alt={t('settings.sources.parentFolderAlt')}
|
||||
className="mr-2 h-4 w-4 flex-shrink-0"
|
||||
/>
|
||||
<span className="truncate">..</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell width="30%" align="left">
|
||||
-
|
||||
</TableCell>
|
||||
<TableCell width="20%" align="right">
|
||||
-
|
||||
</TableCell>
|
||||
<TableCell width="10%" align="right"></TableCell>
|
||||
</TableRow>,
|
||||
]
|
||||
: [];
|
||||
|
||||
// Render directories first, then files
|
||||
@@ -578,10 +573,7 @@ const FileTreeComponent: React.FC<FileTreeComponentProps> = ({
|
||||
const dirStats = calculateDirectoryStats(node as DirectoryStructure);
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
key={itemId}
|
||||
onClick={() => navigateToDirectory(name)}
|
||||
>
|
||||
<TableRow key={itemId} onClick={() => navigateToDirectory(name)}>
|
||||
<TableCell width="40%" align="left">
|
||||
<div className="flex min-w-0 items-center">
|
||||
<img
|
||||
@@ -589,9 +581,7 @@ const FileTreeComponent: React.FC<FileTreeComponentProps> = ({
|
||||
alt={t('settings.sources.folderAlt')}
|
||||
className="mr-2 h-4 w-4 flex-shrink-0"
|
||||
/>
|
||||
<span className="truncate">
|
||||
{name}
|
||||
</span>
|
||||
<span className="truncate">{name}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell width="30%" align="left">
|
||||
@@ -635,10 +625,7 @@ const FileTreeComponent: React.FC<FileTreeComponentProps> = ({
|
||||
const menuRef = getMenuRef(itemId);
|
||||
|
||||
return (
|
||||
<TableRow
|
||||
key={itemId}
|
||||
onClick={() => handleFileClick(name)}
|
||||
>
|
||||
<TableRow key={itemId} onClick={() => handleFileClick(name)}>
|
||||
<TableCell width="40%" align="left">
|
||||
<div className="flex min-w-0 items-center">
|
||||
<img
|
||||
@@ -646,9 +633,7 @@ const FileTreeComponent: React.FC<FileTreeComponentProps> = ({
|
||||
alt={t('settings.sources.fileAlt')}
|
||||
className="mr-2 h-4 w-4 flex-shrink-0"
|
||||
/>
|
||||
<span className="truncate">
|
||||
{name}
|
||||
</span>
|
||||
<span className="truncate">{name}</span>
|
||||
</div>
|
||||
</TableCell>
|
||||
<TableCell width="30%" align="left">
|
||||
@@ -854,9 +839,7 @@ const FileTreeComponent: React.FC<FileTreeComponentProps> = ({
|
||||
</TableHeader>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{renderFileTree(currentDirectory)}
|
||||
</TableBody>
|
||||
<TableBody>{renderFileTree(currentDirectory)}</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</div>
|
||||
|
||||
@@ -159,7 +159,7 @@ export const FileUpload = ({
|
||||
e.stopPropagation();
|
||||
handleRemove();
|
||||
}}
|
||||
className="absolute -right-2 -top-2 rounded-full bg-[#7D54D1] p-1 transition-colors hover:bg-[#714cbc]"
|
||||
className="absolute -top-2 -right-2 rounded-full bg-[#7D54D1] p-1 transition-colors hover:bg-[#714cbc]"
|
||||
>
|
||||
<img src={Cross} alt="remove" className="h-3 w-3" />
|
||||
</button>
|
||||
@@ -215,7 +215,7 @@ export const FileUpload = ({
|
||||
<input {...getInputProps()} />
|
||||
{children || defaultContent}
|
||||
{errors.length > 0 && (
|
||||
<div className="absolute left-0 right-0 mt-[2px] px-4 text-xs text-red-600">
|
||||
<div className="absolute right-0 left-0 mt-[2px] px-4 text-xs text-red-600">
|
||||
{errors.map((error, i) => (
|
||||
<p key={i} className="truncate">
|
||||
{error}
|
||||
|
||||
@@ -2,8 +2,11 @@ import React, { useState, useEffect } from 'react';
|
||||
import useDrivePicker from 'react-google-drive-picker';
|
||||
|
||||
import ConnectorAuth from './ConnectorAuth';
|
||||
import { getSessionToken, setSessionToken, removeSessionToken } from '../utils/providerUtils';
|
||||
|
||||
import {
|
||||
getSessionToken,
|
||||
setSessionToken,
|
||||
removeSessionToken,
|
||||
} from '../utils/providerUtils';
|
||||
|
||||
interface PickerFile {
|
||||
id: string;
|
||||
@@ -31,9 +34,9 @@ const GoogleDrivePicker: React.FC<GoogleDrivePickerProps> = ({
|
||||
const [authError, setAuthError] = useState<string>('');
|
||||
const [accessToken, setAccessToken] = useState<string | null>(null);
|
||||
const [isValidating, setIsValidating] = useState(false);
|
||||
|
||||
|
||||
const [openPicker] = useDrivePicker();
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const sessionToken = getSessionToken('google_drive');
|
||||
if (sessionToken) {
|
||||
@@ -46,14 +49,20 @@ const GoogleDrivePicker: React.FC<GoogleDrivePickerProps> = ({
|
||||
const validateSession = async (sessionToken: string) => {
|
||||
try {
|
||||
const apiHost = import.meta.env.VITE_API_HOST;
|
||||
const validateResponse = await fetch(`${apiHost}/api/connectors/validate-session`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
const validateResponse = await fetch(
|
||||
`${apiHost}/api/connectors/validate-session`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
provider: 'google_drive',
|
||||
session_token: sessionToken,
|
||||
}),
|
||||
},
|
||||
body: JSON.stringify({ provider: 'google_drive', session_token: sessionToken })
|
||||
});
|
||||
);
|
||||
|
||||
if (!validateResponse.ok) {
|
||||
setIsConnected(false);
|
||||
@@ -72,7 +81,10 @@ const GoogleDrivePicker: React.FC<GoogleDrivePickerProps> = ({
|
||||
return true;
|
||||
} else {
|
||||
setIsConnected(false);
|
||||
setAuthError(validateData.error || 'Session expired. Please reconnect your account.');
|
||||
setAuthError(
|
||||
validateData.error ||
|
||||
'Session expired. Please reconnect your account.',
|
||||
);
|
||||
setIsValidating(false);
|
||||
return false;
|
||||
}
|
||||
@@ -87,21 +99,23 @@ const GoogleDrivePicker: React.FC<GoogleDrivePickerProps> = ({
|
||||
|
||||
const handleOpenPicker = async () => {
|
||||
setIsLoading(true);
|
||||
|
||||
|
||||
const sessionToken = getSessionToken('google_drive');
|
||||
|
||||
|
||||
if (!sessionToken) {
|
||||
setAuthError('No valid session found. Please reconnect to Google Drive.');
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!accessToken) {
|
||||
setAuthError('No access token available. Please reconnect to Google Drive.');
|
||||
setAuthError(
|
||||
'No access token available. Please reconnect to Google Drive.',
|
||||
);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
const clientId: string = import.meta.env.VITE_GOOGLE_CLIENT_ID;
|
||||
|
||||
@@ -117,17 +131,18 @@ const GoogleDrivePicker: React.FC<GoogleDrivePickerProps> = ({
|
||||
|
||||
openPicker({
|
||||
clientId: clientId,
|
||||
developerKey: "",
|
||||
developerKey: '',
|
||||
appId: appId,
|
||||
setSelectFolderEnabled: false,
|
||||
viewId: "DOCS",
|
||||
viewId: 'DOCS',
|
||||
showUploadView: false,
|
||||
showUploadFolders: false,
|
||||
supportDrives: false,
|
||||
multiselect: true,
|
||||
token: accessToken,
|
||||
viewMimeTypes: 'application/vnd.google-apps.document,application/vnd.google-apps.presentation,application/vnd.google-apps.spreadsheet,application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/msword,application/vnd.ms-powerpoint,application/vnd.ms-excel,text/plain,text/csv,text/html,text/markdown,text/x-rst,application/json,application/epub+zip,application/rtf,image/jpeg,image/jpg,image/png',
|
||||
callbackFunction: (data:any) => {
|
||||
viewMimeTypes:
|
||||
'application/vnd.google-apps.document,application/vnd.google-apps.presentation,application/vnd.google-apps.spreadsheet,application/pdf,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.openxmlformats-officedocument.presentationml.presentation,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/msword,application/vnd.ms-powerpoint,application/vnd.ms-excel,text/plain,text/csv,text/html,text/markdown,text/x-rst,application/json,application/epub+zip,application/rtf,image/jpeg,image/jpg,image/png',
|
||||
callbackFunction: (data: any) => {
|
||||
setIsLoading(false);
|
||||
if (data.action === 'picked') {
|
||||
const docs = data.docs;
|
||||
@@ -136,14 +151,14 @@ const GoogleDrivePicker: React.FC<GoogleDrivePickerProps> = ({
|
||||
const newFolders: PickerFile[] = [];
|
||||
|
||||
docs.forEach((doc: any) => {
|
||||
const item = {
|
||||
id: doc.id,
|
||||
name: doc.name,
|
||||
mimeType: doc.mimeType,
|
||||
iconUrl: doc.iconUrl || '',
|
||||
description: doc.description,
|
||||
sizeBytes: doc.sizeBytes
|
||||
};
|
||||
const item = {
|
||||
id: doc.id,
|
||||
name: doc.name,
|
||||
mimeType: doc.mimeType,
|
||||
iconUrl: doc.iconUrl || '',
|
||||
description: doc.description,
|
||||
sizeBytes: doc.sizeBytes,
|
||||
};
|
||||
|
||||
if (doc.mimeType === 'application/vnd.google-apps.folder') {
|
||||
newFolders.push(item);
|
||||
@@ -152,20 +167,26 @@ const GoogleDrivePicker: React.FC<GoogleDrivePickerProps> = ({
|
||||
}
|
||||
});
|
||||
|
||||
setSelectedFiles(prevFiles => {
|
||||
const existingFileIds = new Set(prevFiles.map(file => file.id));
|
||||
const uniqueNewFiles = newFiles.filter(file => !existingFileIds.has(file.id));
|
||||
setSelectedFiles((prevFiles) => {
|
||||
const existingFileIds = new Set(prevFiles.map((file) => file.id));
|
||||
const uniqueNewFiles = newFiles.filter(
|
||||
(file) => !existingFileIds.has(file.id),
|
||||
);
|
||||
return [...prevFiles, ...uniqueNewFiles];
|
||||
});
|
||||
|
||||
setSelectedFolders(prevFolders => {
|
||||
const existingFolderIds = new Set(prevFolders.map(folder => folder.id));
|
||||
const uniqueNewFolders = newFolders.filter(folder => !existingFolderIds.has(folder.id));
|
||||
setSelectedFolders((prevFolders) => {
|
||||
const existingFolderIds = new Set(
|
||||
prevFolders.map((folder) => folder.id),
|
||||
);
|
||||
const uniqueNewFolders = newFolders.filter(
|
||||
(folder) => !existingFolderIds.has(folder.id),
|
||||
);
|
||||
return [...prevFolders, ...uniqueNewFolders];
|
||||
});
|
||||
onSelectionChange(
|
||||
[...selectedFiles, ...newFiles].map(file => file.id),
|
||||
[...selectedFolders, ...newFolders].map(folder => folder.id)
|
||||
[...selectedFiles, ...newFiles].map((file) => file.id),
|
||||
[...selectedFolders, ...newFolders].map((folder) => folder.id),
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -186,9 +207,12 @@ const GoogleDrivePicker: React.FC<GoogleDrivePickerProps> = ({
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
body: JSON.stringify({ provider: 'google_drive', session_token: sessionToken })
|
||||
body: JSON.stringify({
|
||||
provider: 'google_drive',
|
||||
session_token: sessionToken,
|
||||
}),
|
||||
});
|
||||
} catch (err) {
|
||||
console.error('Error disconnecting from Google Drive:', err);
|
||||
@@ -207,24 +231,24 @@ const GoogleDrivePicker: React.FC<GoogleDrivePickerProps> = ({
|
||||
|
||||
const ConnectedStateSkeleton = () => (
|
||||
<div className="mb-4">
|
||||
<div className="w-full flex items-center justify-between rounded-[10px] bg-gray-200 dark:bg-gray-700 px-4 py-2 animate-pulse">
|
||||
<div className="flex w-full animate-pulse items-center justify-between rounded-[10px] bg-gray-200 px-4 py-2 dark:bg-gray-700">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="h-4 w-4 bg-gray-300 dark:bg-gray-600 rounded"></div>
|
||||
<div className="h-4 w-32 bg-gray-300 dark:bg-gray-600 rounded"></div>
|
||||
<div className="h-4 w-4 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
<div className="h-4 w-32 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
</div>
|
||||
<div className="h-4 w-16 bg-gray-300 dark:bg-gray-600 rounded"></div>
|
||||
<div className="h-4 w-16 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
const FilesSectionSkeleton = () => (
|
||||
<div className="border border-[#EEE6FF78] rounded-lg dark:border-[#6A6A6A]">
|
||||
<div className="rounded-lg border border-[#EEE6FF78] dark:border-[#6A6A6A]">
|
||||
<div className="p-4">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<div className="h-5 w-24 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
|
||||
<div className="h-8 w-24 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<div className="h-5 w-24 animate-pulse rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
<div className="h-8 w-24 animate-pulse rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
</div>
|
||||
<div className="h-4 w-40 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
|
||||
<div className="h-4 w-40 animate-pulse rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -262,13 +286,13 @@ const GoogleDrivePicker: React.FC<GoogleDrivePickerProps> = ({
|
||||
/>
|
||||
|
||||
{isConnected && (
|
||||
<div className="border border-[#EEE6FF78] rounded-lg dark:border-[#6A6A6A]">
|
||||
<div className="rounded-lg border border-[#EEE6FF78] dark:border-[#6A6A6A]">
|
||||
<div className="p-4">
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<div className="mb-4 flex items-center justify-between">
|
||||
<h3 className="text-sm font-medium">Selected Files</h3>
|
||||
<button
|
||||
onClick={() => handleOpenPicker()}
|
||||
className="bg-[#A076F6] hover:bg-[#8A5FD4] text-white text-sm py-1 px-3 rounded-md"
|
||||
className="rounded-md bg-[#A076F6] px-3 py-1 text-sm text-white hover:bg-[#8A5FD4]"
|
||||
disabled={isLoading}
|
||||
>
|
||||
{isLoading ? 'Loading...' : 'Select Files'}
|
||||
@@ -276,26 +300,42 @@ const GoogleDrivePicker: React.FC<GoogleDrivePickerProps> = ({
|
||||
</div>
|
||||
|
||||
{selectedFiles.length === 0 && selectedFolders.length === 0 ? (
|
||||
<p className="text-gray-600 dark:text-gray-400 text-sm">No files or folders selected</p>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400">
|
||||
No files or folders selected
|
||||
</p>
|
||||
) : (
|
||||
<div className="max-h-60 overflow-y-auto">
|
||||
{selectedFolders.length > 0 && (
|
||||
<div className="mb-2">
|
||||
<h4 className="text-xs font-medium text-gray-500 mb-1">Folders</h4>
|
||||
<h4 className="mb-1 text-xs font-medium text-gray-500">
|
||||
Folders
|
||||
</h4>
|
||||
{selectedFolders.map((folder) => (
|
||||
<div key={folder.id} className="flex items-center p-2 border-b border-gray-200 dark:border-gray-700">
|
||||
<img src={folder.iconUrl} alt="Folder" className="w-5 h-5 mr-2" />
|
||||
<span className="text-sm truncate flex-1">{folder.name}</span>
|
||||
<div
|
||||
key={folder.id}
|
||||
className="flex items-center border-b border-gray-200 p-2 dark:border-gray-700"
|
||||
>
|
||||
<img
|
||||
src={folder.iconUrl}
|
||||
alt="Folder"
|
||||
className="mr-2 h-5 w-5"
|
||||
/>
|
||||
<span className="flex-1 truncate text-sm">
|
||||
{folder.name}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => {
|
||||
const newSelectedFolders = selectedFolders.filter(f => f.id !== folder.id);
|
||||
const newSelectedFolders =
|
||||
selectedFolders.filter(
|
||||
(f) => f.id !== folder.id,
|
||||
);
|
||||
setSelectedFolders(newSelectedFolders);
|
||||
onSelectionChange(
|
||||
selectedFiles.map(f => f.id),
|
||||
newSelectedFolders.map(f => f.id)
|
||||
selectedFiles.map((f) => f.id),
|
||||
newSelectedFolders.map((f) => f.id),
|
||||
);
|
||||
}}
|
||||
className="text-red-500 hover:text-red-700 text-sm ml-2"
|
||||
className="ml-2 text-sm text-red-500 hover:text-red-700"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
@@ -306,21 +346,34 @@ const GoogleDrivePicker: React.FC<GoogleDrivePickerProps> = ({
|
||||
|
||||
{selectedFiles.length > 0 && (
|
||||
<div>
|
||||
<h4 className="text-xs font-medium text-gray-500 mb-1">Files</h4>
|
||||
<h4 className="mb-1 text-xs font-medium text-gray-500">
|
||||
Files
|
||||
</h4>
|
||||
{selectedFiles.map((file) => (
|
||||
<div key={file.id} className="flex items-center p-2 border-b border-gray-200 dark:border-gray-700">
|
||||
<img src={file.iconUrl} alt="File" className="w-5 h-5 mr-2" />
|
||||
<span className="text-sm truncate flex-1">{file.name}</span>
|
||||
<div
|
||||
key={file.id}
|
||||
className="flex items-center border-b border-gray-200 p-2 dark:border-gray-700"
|
||||
>
|
||||
<img
|
||||
src={file.iconUrl}
|
||||
alt="File"
|
||||
className="mr-2 h-5 w-5"
|
||||
/>
|
||||
<span className="flex-1 truncate text-sm">
|
||||
{file.name}
|
||||
</span>
|
||||
<button
|
||||
onClick={() => {
|
||||
const newSelectedFiles = selectedFiles.filter(f => f.id !== file.id);
|
||||
const newSelectedFiles = selectedFiles.filter(
|
||||
(f) => f.id !== file.id,
|
||||
);
|
||||
setSelectedFiles(newSelectedFiles);
|
||||
onSelectionChange(
|
||||
newSelectedFiles.map(f => f.id),
|
||||
selectedFolders.map(f => f.id)
|
||||
newSelectedFiles.map((f) => f.id),
|
||||
selectedFolders.map((f) => f.id),
|
||||
);
|
||||
}}
|
||||
className="text-red-500 hover:text-red-700 text-sm ml-2"
|
||||
className="ml-2 text-sm text-red-500 hover:text-red-700"
|
||||
>
|
||||
Remove
|
||||
</button>
|
||||
|
||||
@@ -59,7 +59,7 @@ const Input = ({
|
||||
{children}
|
||||
</input>
|
||||
{leftIcon && (
|
||||
<div className="absolute left-3 top-1/2 transform -translate-y-1/2 flex items-center justify-center">
|
||||
<div className="absolute top-1/2 left-3 flex -translate-y-1/2 transform items-center justify-center">
|
||||
{leftIcon}
|
||||
</div>
|
||||
)}
|
||||
@@ -69,7 +69,9 @@ const Input = ({
|
||||
className={`absolute select-none ${
|
||||
hasValue ? '-top-2.5 left-3 text-xs' : ''
|
||||
} px-2 transition-all peer-placeholder-shown:top-2.5 ${
|
||||
leftIcon ? 'peer-placeholder-shown:left-7' : 'peer-placeholder-shown:left-3'
|
||||
leftIcon
|
||||
? 'peer-placeholder-shown:left-7'
|
||||
: 'peer-placeholder-shown:left-3'
|
||||
} peer-placeholder-shown:${
|
||||
textSizeStyles[textSize]
|
||||
} text-gray-4000 pointer-events-none cursor-none peer-focus:-top-2.5 peer-focus:left-3 peer-focus:text-xs dark:text-gray-400 ${labelBgClassName} max-w-[calc(100%-24px)] overflow-hidden text-ellipsis whitespace-nowrap`}
|
||||
|
||||
@@ -15,7 +15,7 @@ export default function ShareButton({ conversationId }: ShareButtonProps) {
|
||||
onClick={() => {
|
||||
setShareModalState(true);
|
||||
}}
|
||||
className="absolute right-20 top-4 z-20 rounded-full hover:bg-bright-gray dark:hover:bg-[#28292E]"
|
||||
className="hover:bg-bright-gray absolute top-4 right-20 z-20 rounded-full dark:hover:bg-[#28292E]"
|
||||
>
|
||||
<img
|
||||
className="m-2 h-5 w-5 filter dark:invert"
|
||||
|
||||
@@ -189,19 +189,19 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
|
||||
{Array.from({ length: count }).map((_, index) => (
|
||||
<div
|
||||
key={`chunk-skel-${index}`}
|
||||
className="relative flex h-[197px] flex-col rounded-[5.86px] border border-[#D1D9E0] dark:border-[#6A6A6A] overflow-hidden w-full max-w-[487px] animate-pulse"
|
||||
className="relative flex h-[197px] w-full max-w-[487px] animate-pulse flex-col overflow-hidden rounded-[5.86px] border border-[#D1D9E0] dark:border-[#6A6A6A]"
|
||||
>
|
||||
<div className="w-full">
|
||||
<div className="flex w-full items-center justify-between border-b border-[#D1D9E0] bg-[#F6F8FA] dark:bg-[#27282D] dark:border-[#6A6A6A] px-4 py-3">
|
||||
<div className="h-4 bg-gray-300 dark:bg-gray-600 rounded w-20"></div>
|
||||
<div className="flex w-full items-center justify-between border-b border-[#D1D9E0] bg-[#F6F8FA] px-4 py-3 dark:border-[#6A6A6A] dark:bg-[#27282D]">
|
||||
<div className="h-4 w-20 rounded bg-gray-300 dark:bg-gray-600"></div>
|
||||
</div>
|
||||
<div className="px-4 pt-4 pb-6 space-y-3">
|
||||
<div className="h-3 bg-gray-200 dark:bg-gray-700 rounded w-full"></div>
|
||||
<div className="h-3 bg-gray-200 dark:bg-gray-700 rounded w-11/12"></div>
|
||||
<div className="h-3 bg-gray-200 dark:bg-gray-700 rounded w-5/6"></div>
|
||||
<div className="h-3 bg-gray-200 dark:bg-gray-700 rounded w-4/5"></div>
|
||||
<div className="h-3 bg-gray-200 dark:bg-gray-700 rounded w-3/4"></div>
|
||||
<div className="h-3 bg-gray-200 dark:bg-gray-700 rounded w-2/3"></div>
|
||||
<div className="space-y-3 px-4 pt-4 pb-6">
|
||||
<div className="h-3 w-full rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
<div className="h-3 w-11/12 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-4/5 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 className="h-3 w-2/3 rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -214,24 +214,24 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
|
||||
{Array.from({ length: count }).map((_, idx) => (
|
||||
<div
|
||||
key={`source-skel-${idx}`}
|
||||
className="flex h-[130px] w-full flex-col rounded-2xl bg-[#F9F9F9] dark:bg-[#383838] p-3 animate-pulse"
|
||||
className="flex h-[130px] w-full animate-pulse flex-col rounded-2xl bg-[#F9F9F9] p-3 dark:bg-[#383838]"
|
||||
>
|
||||
<div className="w-full flex-1">
|
||||
<div className="flex w-full items-center justify-between gap-2">
|
||||
<div className="flex-1">
|
||||
<div className="h-[13px] w-full rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
</div>
|
||||
<div className="w-6 h-6 rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
<div className="h-6 w-6 rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-start justify-start gap-1 pt-3">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<div className="w-3 h-3 rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
<div className="mb-1 flex items-center gap-2">
|
||||
<div className="h-3 w-3 rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
<div className="h-[12px] w-20 rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
<div className="h-3 w-3 rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
<div className="h-[12px] w-16 rounded bg-gray-200 dark:bg-gray-700"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -251,7 +251,6 @@ const SkeletonLoader: React.FC<SkeletonLoaderProps> = ({
|
||||
sourceCards: renderSourceCards,
|
||||
};
|
||||
|
||||
|
||||
const render = componentMap[component] || componentMap.default;
|
||||
|
||||
return <>{render()}</>;
|
||||
|
||||
@@ -6,7 +6,6 @@ interface TableProps {
|
||||
minWidth?: string;
|
||||
}
|
||||
|
||||
|
||||
interface TableContainerProps {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
@@ -34,12 +33,15 @@ interface TableCellProps {
|
||||
align?: 'left' | 'right' | 'center';
|
||||
}
|
||||
|
||||
const TableContainer = React.forwardRef<HTMLDivElement, TableContainerProps>(({
|
||||
children,
|
||||
className = '',
|
||||
height = 'auto',
|
||||
bordered = true
|
||||
}, ref) => {
|
||||
function TableContainerBase(
|
||||
{
|
||||
children,
|
||||
className = '',
|
||||
height = 'auto',
|
||||
bordered = true,
|
||||
}: TableContainerProps,
|
||||
ref: React.Ref<HTMLDivElement>,
|
||||
) {
|
||||
return (
|
||||
<div className={`relative rounded-[6px] ${className}`}>
|
||||
<div
|
||||
@@ -47,32 +49,38 @@ const TableContainer = React.forwardRef<HTMLDivElement, TableContainerProps>(({
|
||||
className={`w-full overflow-x-auto rounded-[6px] bg-transparent ${bordered ? 'border border-[#D7D7D7] dark:border-[#6A6A6A]' : ''}`}
|
||||
style={{
|
||||
maxHeight: height === 'auto' ? undefined : height,
|
||||
overflowY: height === 'auto' ? 'hidden' : 'auto'
|
||||
overflowY: height === 'auto' ? 'hidden' : 'auto',
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});;
|
||||
}
|
||||
|
||||
const TableContainer = React.forwardRef<HTMLDivElement, TableContainerProps>(
|
||||
TableContainerBase,
|
||||
);
|
||||
TableContainer.displayName = 'TableContainer';
|
||||
|
||||
const Table: React.FC<TableProps> = ({
|
||||
children,
|
||||
className = '',
|
||||
minWidth = 'min-w-[600px]'
|
||||
minWidth = 'min-w-[600px]',
|
||||
}) => {
|
||||
return (
|
||||
<table className={`w-full table-auto border-collapse bg-transparent ${minWidth} ${className}`}>
|
||||
<table
|
||||
className={`w-full table-auto border-collapse bg-transparent ${minWidth} ${className}`}
|
||||
>
|
||||
{children}
|
||||
</table>
|
||||
);
|
||||
};
|
||||
const TableHead: React.FC<TableHeadProps> = ({ children, className = '' }) => {
|
||||
return (
|
||||
<thead className={`
|
||||
sticky top-0 z-10
|
||||
bg-gray-100 dark:bg-[#27282D]
|
||||
${className}
|
||||
`}>
|
||||
<thead
|
||||
className={`sticky top-0 z-10 bg-gray-100 dark:bg-[#27282D] ${className} `}
|
||||
>
|
||||
{children}
|
||||
</thead>
|
||||
);
|
||||
@@ -86,12 +94,20 @@ const TableBody: React.FC<TableHeadProps> = ({ children, className = '' }) => {
|
||||
);
|
||||
};
|
||||
|
||||
const TableRow: React.FC<TableRowProps> = ({ children, className = '', onClick }) => {
|
||||
const baseClasses = "border-b border-[#D7D7D7] hover:bg-[#ECEEEF] dark:border-[#6A6A6A] dark:hover:bg-[#27282D]";
|
||||
const cursorClass = onClick ? "cursor-pointer" : "";
|
||||
const TableRow: React.FC<TableRowProps> = ({
|
||||
children,
|
||||
className = '',
|
||||
onClick,
|
||||
}) => {
|
||||
const baseClasses =
|
||||
'border-b border-[#D7D7D7] hover:bg-[#ECEEEF] dark:border-[#6A6A6A] dark:hover:bg-[#27282D]';
|
||||
const cursorClass = onClick ? 'cursor-pointer' : '';
|
||||
|
||||
return (
|
||||
<tr className={`${baseClasses} ${cursorClass} ${className}`} onClick={onClick}>
|
||||
<tr
|
||||
className={`${baseClasses} ${cursorClass} ${className}`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</tr>
|
||||
);
|
||||
@@ -102,7 +118,7 @@ const TableHeader: React.FC<TableCellProps> = ({
|
||||
className = '',
|
||||
minWidth,
|
||||
width,
|
||||
align = 'left'
|
||||
align = 'left',
|
||||
}) => {
|
||||
const getAlignmentClass = () => {
|
||||
switch (align) {
|
||||
@@ -133,7 +149,7 @@ const TableCell: React.FC<TableCellProps> = ({
|
||||
className = '',
|
||||
minWidth,
|
||||
width,
|
||||
align = 'left'
|
||||
align = 'left',
|
||||
}) => {
|
||||
const getAlignmentClass = () => {
|
||||
switch (align) {
|
||||
|
||||
@@ -68,7 +68,7 @@ export default function SpeakButton({
|
||||
<div
|
||||
className={`flex items-center justify-center rounded-full p-2 ${
|
||||
isSpeakHovered
|
||||
? `bg-[#EEEEEE] dark:bg-purple-taupe`
|
||||
? `dark:bg-purple-taupe bg-[#EEEEEE]`
|
||||
: `bg-[${colorLight ? colorLight : '#FFFFFF'}] dark:bg-[${colorDark ? colorDark : 'transparent'}]`
|
||||
}`}
|
||||
>
|
||||
|
||||
@@ -157,7 +157,7 @@ export default function ConversationMessages({
|
||||
if (query.error) {
|
||||
const retryButton = (
|
||||
<button
|
||||
className="flex items-center justify-center gap-3 self-center rounded-full px-5 py-3 text-lg text-gray-500 transition-colors delay-100 hover:border-gray-500 disabled:cursor-not-allowed dark:text-bright-gray"
|
||||
className="dark:text-bright-gray flex items-center justify-center gap-3 self-center rounded-full px-5 py-3 text-lg text-gray-500 transition-colors delay-100 hover:border-gray-500 disabled:cursor-not-allowed"
|
||||
disabled={status === 'loading'}
|
||||
onClick={() => {
|
||||
const questionToRetry = queries[index].prompt;
|
||||
@@ -199,12 +199,12 @@ export default function ConversationMessages({
|
||||
scrollConversationToBottom();
|
||||
}}
|
||||
aria-label={t('Scroll to bottom') || 'Scroll to bottom'}
|
||||
className="fixed bottom-40 right-14 z-10 flex h-7 w-7 items-center justify-center rounded-full border-[0.5px] border-gray-alpha bg-gray-100 bg-opacity-50 dark:bg-gunmetal md:h-9 md:w-9 md:bg-opacity-100"
|
||||
className="border-gray-alpha bg-opacity-50 dark:bg-gunmetal md:bg-opacity-100 fixed right-14 bottom-40 z-10 flex h-7 w-7 items-center justify-center rounded-full border-[0.5px] bg-gray-100 md:h-9 md:w-9"
|
||||
>
|
||||
<img
|
||||
src={ArrowDown}
|
||||
alt="arrow down"
|
||||
className="h-4 w-4 opacity-50 filter dark:invert md:h-5 md:w-5"
|
||||
className="h-4 w-4 opacity-50 filter md:h-5 md:w-5 dark:invert"
|
||||
/>
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -43,7 +43,7 @@ export default function AddActionModal({
|
||||
className="sm:w-[512px]"
|
||||
>
|
||||
<div>
|
||||
<h2 className="px-3 text-xl font-semibold text-jet dark:text-bright-gray">
|
||||
<h2 className="text-jet dark:text-bright-gray px-3 text-xl font-semibold">
|
||||
New Action
|
||||
</h2>
|
||||
<div className="relative mt-6 px-3">
|
||||
@@ -61,7 +61,7 @@ export default function AddActionModal({
|
||||
required={true}
|
||||
/>
|
||||
<p
|
||||
className={`ml-1 mt-2 text-xs italic ${
|
||||
className={`mt-2 ml-1 text-xs italic ${
|
||||
functionNameError ? 'text-red-500' : 'text-gray-500'
|
||||
}`}
|
||||
>
|
||||
@@ -73,7 +73,7 @@ export default function AddActionModal({
|
||||
<div className="mt-3 flex flex-row-reverse gap-1 px-3">
|
||||
<button
|
||||
onClick={handleAddAction}
|
||||
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-violets-are-blue"
|
||||
className="bg-purple-30 hover:bg-violets-are-blue rounded-3xl px-5 py-2 text-sm text-white transition-all"
|
||||
>
|
||||
Add
|
||||
</button>
|
||||
@@ -83,7 +83,7 @@ export default function AddActionModal({
|
||||
setModalState('INACTIVE');
|
||||
setActionName('');
|
||||
}}
|
||||
className="cursor-pointer rounded-3xl px-5 py-2 text-sm font-medium hover:bg-gray-100 dark:bg-transparent dark:text-light-gray dark:hover:bg-[#767183]/50"
|
||||
className="dark:text-light-gray cursor-pointer rounded-3xl px-5 py-2 text-sm font-medium hover:bg-gray-100 dark:bg-transparent dark:hover:bg-[#767183]/50"
|
||||
>
|
||||
{t('modals.configTool.closeButton')}
|
||||
</button>
|
||||
|
||||
@@ -85,19 +85,19 @@ export default function AgentDetailsModal({
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
<h2 className="text-xl font-semibold text-jet dark:text-bright-gray">
|
||||
<h2 className="text-jet dark:text-bright-gray text-xl font-semibold">
|
||||
Access Details
|
||||
</h2>
|
||||
<div className="mt-8 flex flex-col gap-6">
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="text-base font-semibold text-jet dark:text-bright-gray">
|
||||
<h2 className="text-jet dark:text-bright-gray text-base font-semibold">
|
||||
Public Link
|
||||
</h2>
|
||||
</div>
|
||||
{sharedToken ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<p className="inline break-all font-roboto text-[14px] font-medium leading-normal text-gray-700 dark:text-[#ECECF1]">
|
||||
<p className="font-roboto inline text-[14px] leading-normal font-medium break-all text-gray-700 dark:text-[#ECECF1]">
|
||||
<a
|
||||
href={`${baseURL}/shared/agent/${sharedToken}`}
|
||||
target="_blank"
|
||||
@@ -113,7 +113,7 @@ export default function AgentDetailsModal({
|
||||
</p>
|
||||
<a
|
||||
href="https://docs.docsgpt.cloud/Agents/basics#core-components-of-an-agent"
|
||||
className="flex w-fit items-center gap-1 text-purple-30 hover:underline"
|
||||
className="text-purple-30 flex w-fit items-center gap-1 hover:underline"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@@ -127,7 +127,7 @@ export default function AgentDetailsModal({
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
className="flex w-28 items-center justify-center rounded-3xl border border-solid border-purple-30 px-5 py-2 text-sm font-medium text-purple-30 transition-colors hover:bg-purple-30 hover:text-white"
|
||||
className="border-purple-30 text-purple-30 hover:bg-purple-30 flex w-28 items-center justify-center rounded-3xl border border-solid px-5 py-2 text-sm font-medium transition-colors hover:text-white"
|
||||
onClick={handleGeneratePublicLink}
|
||||
>
|
||||
{loadingStates.publicLink ? (
|
||||
@@ -139,13 +139,13 @@ export default function AgentDetailsModal({
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<h2 className="text-base font-semibold text-jet dark:text-bright-gray">
|
||||
<h2 className="text-jet dark:text-bright-gray text-base font-semibold">
|
||||
API Key
|
||||
</h2>
|
||||
{apiKey ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="break-all font-roboto text-[14px] font-medium leading-normal text-gray-700 dark:text-[#ECECF1]">
|
||||
<div className="font-roboto text-[14px] leading-normal font-medium break-all text-gray-700 dark:text-[#ECECF1]">
|
||||
{apiKey}
|
||||
{!apiKey.includes('...') && (
|
||||
<CopyButton
|
||||
@@ -158,7 +158,7 @@ export default function AgentDetailsModal({
|
||||
{!apiKey.includes('...') && (
|
||||
<a
|
||||
href={`https://widget.docsgpt.cloud/?api-key=${apiKey}`}
|
||||
className="group ml-8 flex w-[101px] items-center justify-center gap-1 rounded-[62px] border border-purple-30 py-1.5 text-sm font-medium text-purple-30 transition-colors hover:bg-purple-30 hover:text-white"
|
||||
className="group border-purple-30 text-purple-30 hover:bg-purple-30 ml-8 flex w-[101px] items-center justify-center gap-1 rounded-[62px] border py-1.5 text-sm font-medium transition-colors hover:text-white"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@@ -173,20 +173,20 @@ export default function AgentDetailsModal({
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<button className="w-28 rounded-3xl border border-solid border-purple-30 px-5 py-2 text-sm font-medium text-purple-30 transition-colors hover:bg-purple-30 hover:text-white">
|
||||
<button className="border-purple-30 text-purple-30 hover:bg-purple-30 w-28 rounded-3xl border border-solid px-5 py-2 text-sm font-medium transition-colors hover:text-white">
|
||||
Generate
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<h2 className="text-base font-semibold text-jet dark:text-bright-gray">
|
||||
<h2 className="text-jet dark:text-bright-gray text-base font-semibold">
|
||||
Webhook URL
|
||||
</h2>
|
||||
</div>
|
||||
{webhookUrl ? (
|
||||
<div className="flex flex-col gap-2">
|
||||
<p className="break-all font-roboto text-[14px] font-medium leading-normal text-gray-700 dark:text-[#ECECF1]">
|
||||
<p className="font-roboto text-[14px] leading-normal font-medium break-all text-gray-700 dark:text-[#ECECF1]">
|
||||
<a href={webhookUrl} target="_blank" rel="noreferrer">
|
||||
{webhookUrl}
|
||||
</a>
|
||||
@@ -198,7 +198,7 @@ export default function AgentDetailsModal({
|
||||
</p>
|
||||
<a
|
||||
href="https://docs.docsgpt.cloud/Agents/basics#core-components-of-an-agent"
|
||||
className="flex w-fit items-center gap-1 text-purple-30 hover:underline"
|
||||
className="text-purple-30 flex w-fit items-center gap-1 hover:underline"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
@@ -212,7 +212,7 @@ export default function AgentDetailsModal({
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
className="flex w-28 items-center justify-center rounded-3xl border border-solid border-purple-30 px-5 py-2 text-sm font-medium text-purple-30 transition-colors hover:bg-purple-30 hover:text-white"
|
||||
className="border-purple-30 text-purple-30 hover:bg-purple-30 flex w-28 items-center justify-center rounded-3xl border border-solid px-5 py-2 text-sm font-medium transition-colors hover:text-white"
|
||||
onClick={handleGenerateWebhook}
|
||||
>
|
||||
{loadingStates.webhook ? (
|
||||
|
||||
@@ -53,7 +53,7 @@ export default function ConfigToolModal({
|
||||
return (
|
||||
<WrapperModal close={() => setModalState('INACTIVE')}>
|
||||
<div>
|
||||
<h2 className="px-3 text-xl font-semibold text-jet dark:text-bright-gray">
|
||||
<h2 className="text-jet dark:text-bright-gray px-3 text-xl font-semibold">
|
||||
{t('modals.configTool.title')}
|
||||
</h2>
|
||||
<p className="mt-5 px-3 text-sm text-gray-600 dark:text-gray-400">
|
||||
@@ -85,13 +85,13 @@ export default function ConfigToolModal({
|
||||
onClick={() => {
|
||||
tool && handleAddTool(tool);
|
||||
}}
|
||||
className="rounded-3xl bg-purple-30 px-5 py-2 text-sm text-white transition-all hover:bg-violets-are-blue"
|
||||
className="bg-purple-30 hover:bg-violets-are-blue rounded-3xl px-5 py-2 text-sm text-white transition-all"
|
||||
>
|
||||
{t('modals.configTool.addButton')}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setModalState('INACTIVE')}
|
||||
className="cursor-pointer rounded-3xl px-5 py-2 text-sm font-medium hover:bg-gray-100 dark:bg-transparent dark:text-light-gray dark:hover:bg-[#767183]/50"
|
||||
className="dark:text-light-gray cursor-pointer rounded-3xl px-5 py-2 text-sm font-medium hover:bg-gray-100 dark:bg-transparent dark:hover:bg-[#767183]/50"
|
||||
>
|
||||
{t('modals.configTool.closeButton')}
|
||||
</button>
|
||||
|
||||
@@ -76,11 +76,11 @@ export default function CreateAPIKeyModal({
|
||||
return (
|
||||
<WrapperModal close={close} className="p-4">
|
||||
<div className="mb-6">
|
||||
<span className="text-xl text-jet dark:text-bright-gray">
|
||||
<span className="text-jet dark:text-bright-gray text-xl">
|
||||
{t('modals.createAPIKey.label')}
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative mb-4 mt-5">
|
||||
<div className="relative mt-5 mb-4">
|
||||
<Input
|
||||
type="text"
|
||||
className="rounded-md"
|
||||
@@ -117,7 +117,7 @@ export default function CreateAPIKeyModal({
|
||||
/>
|
||||
</div>
|
||||
<div className="my-4">
|
||||
<p className="mb-2 ml-2 font-semibold text-jet dark:text-bright-gray">
|
||||
<p className="text-jet dark:text-bright-gray mb-2 ml-2 font-semibold">
|
||||
{t('modals.createAPIKey.chunks')}
|
||||
</p>
|
||||
<Dropdown
|
||||
@@ -146,7 +146,7 @@ export default function CreateAPIKeyModal({
|
||||
createAPIKey(payload);
|
||||
}
|
||||
}}
|
||||
className="float-right mt-4 rounded-full bg-purple-30 px-5 py-2 text-sm text-white hover:bg-violets-are-blue disabled:opacity-50"
|
||||
className="bg-purple-30 hover:bg-violets-are-blue float-right mt-4 rounded-full px-5 py-2 text-sm text-white disabled:opacity-50"
|
||||
>
|
||||
{t('modals.createAPIKey.create')}
|
||||
</button>
|
||||
|
||||
@@ -24,11 +24,11 @@ export default function JWTModal({
|
||||
close={() => undefined}
|
||||
>
|
||||
<div className="mb-6">
|
||||
<span className="text-lg text-jet dark:text-bright-gray">
|
||||
<span className="text-jet dark:text-bright-gray text-lg">
|
||||
Add JWT Token
|
||||
</span>
|
||||
</div>
|
||||
<div className="relative mb-4 mt-5">
|
||||
<div className="relative mt-5 mb-4">
|
||||
<Input
|
||||
name="JWT Token"
|
||||
type="text"
|
||||
@@ -41,7 +41,7 @@ export default function JWTModal({
|
||||
<button
|
||||
disabled={jwtToken.length === 0}
|
||||
onClick={handleTokenSubmit.bind(null, jwtToken)}
|
||||
className="float-right mt-4 rounded-full bg-purple-30 px-5 py-2 text-sm text-white hover:bg-[#6F3FD1] disabled:opacity-50"
|
||||
className="bg-purple-30 float-right mt-4 rounded-full px-5 py-2 text-sm text-white hover:bg-[#6F3FD1] disabled:opacity-50"
|
||||
>
|
||||
Save Token
|
||||
</button>
|
||||
|
||||
@@ -18,23 +18,23 @@ export default function SaveAPIKeyModal({
|
||||
|
||||
return (
|
||||
<WrapperModal close={close}>
|
||||
<h1 className="my-0 text-xl font-medium text-jet dark:text-bright-gray">
|
||||
<h1 className="text-jet dark:text-bright-gray my-0 text-xl font-medium">
|
||||
{t('modals.saveKey.note')}
|
||||
</h1>
|
||||
<h3 className="text-sm font-normal text-outer-space dark:text-silver">
|
||||
<h3 className="text-outer-space dark:text-silver text-sm font-normal">
|
||||
{t('modals.saveKey.disclaimer')}
|
||||
</h3>
|
||||
<div className="flex justify-between py-2">
|
||||
<div>
|
||||
<h2 className="text-base font-semibold text-jet dark:text-bright-gray">
|
||||
<h2 className="text-jet dark:text-bright-gray text-base font-semibold">
|
||||
API Key
|
||||
</h2>
|
||||
<span className="text-sm font-normal leading-7 text-jet dark:text-bright-gray">
|
||||
<span className="text-jet dark:text-bright-gray text-sm leading-7 font-normal">
|
||||
{apiKey}
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
className="my-1 h-10 w-20 rounded-full border border-solid border-violets-are-blue p-2 text-sm text-violets-are-blue hover:bg-violets-are-blue hover:text-white"
|
||||
className="border-violets-are-blue text-violets-are-blue hover:bg-violets-are-blue my-1 h-10 w-20 rounded-full border border-solid p-2 text-sm hover:text-white"
|
||||
onClick={handleCopyKey}
|
||||
>
|
||||
{isCopied ? t('modals.saveKey.copied') : t('modals.saveKey.copy')}
|
||||
@@ -42,7 +42,7 @@ export default function SaveAPIKeyModal({
|
||||
</div>
|
||||
<button
|
||||
onClick={close}
|
||||
className="rounded-full bg-philippine-yellow px-4 py-3 font-medium text-black hover:bg-[#E6B91A]"
|
||||
className="bg-philippine-yellow rounded-full px-4 py-3 font-medium text-black hover:bg-[#E6B91A]"
|
||||
>
|
||||
{t('modals.saveKey.confirm')}
|
||||
</button>
|
||||
|
||||
@@ -45,7 +45,7 @@ export default function WrapperModal({
|
||||
<div className="fixed top-0 left-0 z-30 flex h-screen w-screen items-center justify-center">
|
||||
<div
|
||||
ref={modalRef}
|
||||
className={`relative rounded-2xl bg-white dark:bg-[#26272E] p-8 shadow-[0px_4px_40px_-3px_#0000001A] ${className}`}
|
||||
className={`relative rounded-2xl bg-white p-8 shadow-[0px_4px_40px_-3px_#0000001A] dark:bg-[#26272E] ${className}`}
|
||||
>
|
||||
{!isPerformingTask && (
|
||||
<button
|
||||
@@ -55,7 +55,11 @@ export default function WrapperModal({
|
||||
<img className="filter dark:invert" src={Exit} alt="Close" />
|
||||
</button>
|
||||
)}
|
||||
<div className={`overflow-y-auto no-scrollbar text-[#18181B] dark:text-[#ECECF1] ${contentClassName}`}>{children}</div>
|
||||
<div
|
||||
className={`no-scrollbar overflow-y-auto text-[#18181B] dark:text-[#ECECF1] ${contentClassName}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -177,9 +177,9 @@ export default function Analytics({ agentId }: AnalyticsProps) {
|
||||
<div className="mt-12">
|
||||
{/* Messages Analytics */}
|
||||
<div className="mt-8 flex w-full flex-col gap-3 [@media(min-width:1080px)]:flex-row">
|
||||
<div className="h-[345px] w-full overflow-hidden rounded-2xl border border-silver px-6 py-5 dark:border-silver/40 [@media(min-width:1080px)]:w-1/2">
|
||||
<div className="border-silver dark:border-silver/40 h-[345px] w-full overflow-hidden rounded-2xl border px-6 py-5 [@media(min-width:1080px)]:w-1/2">
|
||||
<div className="flex flex-row items-center justify-start gap-3">
|
||||
<p className="font-bold text-jet dark:text-bright-gray">
|
||||
<p className="text-jet dark:text-bright-gray font-bold">
|
||||
{t('settings.analytics.messages')}
|
||||
</p>
|
||||
<Dropdown
|
||||
@@ -225,9 +225,9 @@ export default function Analytics({ agentId }: AnalyticsProps) {
|
||||
</div>
|
||||
|
||||
{/* Token Usage Analytics */}
|
||||
<div className="h-[345px] w-full overflow-hidden rounded-2xl border border-silver px-6 py-5 dark:border-silver/40 [@media(min-width:1080px)]:w-1/2">
|
||||
<div className="border-silver dark:border-silver/40 h-[345px] w-full overflow-hidden rounded-2xl border px-6 py-5 [@media(min-width:1080px)]:w-1/2">
|
||||
<div className="flex flex-row items-center justify-start gap-3">
|
||||
<p className="font-bold text-jet dark:text-bright-gray">
|
||||
<p className="text-jet dark:text-bright-gray font-bold">
|
||||
{t('settings.analytics.tokenUsage')}
|
||||
</p>
|
||||
<Dropdown
|
||||
@@ -275,9 +275,9 @@ export default function Analytics({ agentId }: AnalyticsProps) {
|
||||
|
||||
{/* Feedback Analytics */}
|
||||
<div className="mt-8 flex w-full flex-col gap-3">
|
||||
<div className="h-[345px] w-full overflow-hidden rounded-2xl border border-silver px-6 py-5 dark:border-silver/40">
|
||||
<div className="border-silver dark:border-silver/40 h-[345px] w-full overflow-hidden rounded-2xl border px-6 py-5">
|
||||
<div className="flex flex-row items-center justify-start gap-3">
|
||||
<p className="font-bold text-jet dark:text-bright-gray">
|
||||
<p className="text-jet dark:text-bright-gray font-bold">
|
||||
{t('settings.analytics.userFeedback')}
|
||||
</p>
|
||||
<Dropdown
|
||||
|
||||
@@ -171,7 +171,7 @@ export default function Tools() {
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
className="flex h-[32px] min-w-[108px] items-center justify-center whitespace-normal rounded-full bg-purple-30 px-4 text-sm text-white hover:bg-violets-are-blue"
|
||||
className="bg-purple-30 hover:bg-violets-are-blue flex h-[32px] min-w-[108px] items-center justify-center rounded-full px-4 text-sm whitespace-normal text-white"
|
||||
onClick={() => {
|
||||
setAddToolModalState('ACTIVE');
|
||||
}}
|
||||
@@ -179,7 +179,7 @@ export default function Tools() {
|
||||
{t('settings.tools.addTool')}
|
||||
</button>
|
||||
</div>
|
||||
<div className="mb-8 mt-5 border-b border-light-silver dark:border-dim-gray" />
|
||||
<div className="border-light-silver dark:border-dim-gray 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">
|
||||
@@ -219,7 +219,7 @@ export default function Tools() {
|
||||
activeMenuId === tool.id ? null : tool.id,
|
||||
);
|
||||
}}
|
||||
className="absolute right-4 top-4 z-10 cursor-pointer"
|
||||
className="absolute top-4 right-4 z-10 cursor-pointer"
|
||||
>
|
||||
<img
|
||||
src={ThreeDotsIcon}
|
||||
@@ -248,16 +248,16 @@ export default function Tools() {
|
||||
<div className="mt-[9px]">
|
||||
<p
|
||||
title={tool.customName || tool.displayName}
|
||||
className="truncate px-1 text-[13px] font-semibold capitalize leading-relaxed text-raisin-black-light dark:text-bright-gray"
|
||||
className="text-raisin-black-light dark:text-bright-gray truncate px-1 text-[13px] leading-relaxed font-semibold capitalize"
|
||||
>
|
||||
{tool.customName || tool.displayName}
|
||||
</p>
|
||||
<p className="mt-1 h-24 overflow-auto px-1 text-[12px] leading-relaxed text-old-silver dark:text-sonic-silver-light">
|
||||
<p className="text-old-silver dark:text-sonic-silver-light mt-1 h-24 overflow-auto px-1 text-[12px] leading-relaxed">
|
||||
{tool.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="absolute bottom-4 right-4">
|
||||
<div className="absolute right-4 bottom-4">
|
||||
<ToggleSwitch
|
||||
checked={tool.status}
|
||||
onChange={(checked) =>
|
||||
|
||||
@@ -50,7 +50,7 @@ const Widgets: React.FC<{
|
||||
return (
|
||||
<div>
|
||||
<div className="mt-[59px]">
|
||||
<p className="font-bold text-jet">Widget Source</p>
|
||||
<p className="text-jet font-bold">Widget Source</p>
|
||||
<Dropdown
|
||||
options={widgetSources}
|
||||
selectedValue={selectedWidgetSource}
|
||||
@@ -58,7 +58,7 @@ const Widgets: React.FC<{
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<p className="font-bold text-jet">Widget Method</p>
|
||||
<p className="text-jet font-bold">Widget Method</p>
|
||||
<Dropdown
|
||||
options={widgetMethods}
|
||||
selectedValue={selectedWidgetMethod}
|
||||
@@ -66,7 +66,7 @@ const Widgets: React.FC<{
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-5">
|
||||
<p className="font-bold text-jet">Widget Type</p>
|
||||
<p className="text-jet font-bold">Widget Type</p>
|
||||
<Dropdown
|
||||
options={widgetTypes}
|
||||
selectedValue={selectedWidgetType}
|
||||
@@ -74,7 +74,7 @@ const Widgets: React.FC<{
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-6">
|
||||
<p className="font-bold text-jet">Widget Code Snippet</p>
|
||||
<p className="text-jet font-bold">Widget Code Snippet</p>
|
||||
<textarea
|
||||
rows={4}
|
||||
value={widgetCode}
|
||||
|
||||
@@ -38,8 +38,7 @@ export default function Settings() {
|
||||
|
||||
const getActiveTabFromPath = () => {
|
||||
const path = location.pathname;
|
||||
if (path.includes('/settings/sources'))
|
||||
return t('settings.sources.label');
|
||||
if (path.includes('/settings/sources')) return t('settings.sources.label');
|
||||
if (path.includes('/settings/analytics'))
|
||||
return t('settings.analytics.label');
|
||||
if (path.includes('/settings/logs')) return t('settings.logs.label');
|
||||
@@ -53,8 +52,7 @@ export default function Settings() {
|
||||
const handleTabChange = (tab: string) => {
|
||||
setActiveTab(tab);
|
||||
if (tab === t('settings.general.label')) navigate('/settings');
|
||||
else if (tab === t('settings.sources.label'))
|
||||
navigate('/settings/sources');
|
||||
else if (tab === t('settings.sources.label')) navigate('/settings/sources');
|
||||
else if (tab === t('settings.analytics.label'))
|
||||
navigate('/settings/analytics');
|
||||
else if (tab === t('settings.logs.label')) navigate('/settings/logs');
|
||||
@@ -103,7 +101,7 @@ export default function Settings() {
|
||||
|
||||
return (
|
||||
<div className="h-full overflow-auto p-4 md:p-12">
|
||||
<p className="text-2xl font-bold text-eerie-black dark:text-bright-gray">
|
||||
<p className="text-eerie-black dark:text-bright-gray text-2xl font-bold">
|
||||
{t('settings.label')}
|
||||
</p>
|
||||
<SettingsBar
|
||||
|
||||
@@ -9,7 +9,7 @@ import Dropdown from '../components/Dropdown';
|
||||
import Input from '../components/Input';
|
||||
import ToggleSwitch from '../components/ToggleSwitch';
|
||||
import WrapperModal from '../modals/WrapperModal';
|
||||
import { ActiveState, Doc } from '../models/misc';
|
||||
import { ActiveState, Doc } from '../models/misc';
|
||||
|
||||
import { getDocs } from '../preferences/preferenceApi';
|
||||
import {
|
||||
@@ -18,14 +18,15 @@ import {
|
||||
setSelectedDocs,
|
||||
setSourceDocs,
|
||||
} from '../preferences/preferenceSlice';
|
||||
import { IngestorDefaultConfigs, IngestorFormSchemas, getIngestorSchema, IngestorOption } from '../upload/types/ingestor';
|
||||
import {
|
||||
FormField,
|
||||
IngestorConfig,
|
||||
IngestorType,
|
||||
} from './types/ingestor';
|
||||
IngestorDefaultConfigs,
|
||||
IngestorFormSchemas,
|
||||
getIngestorSchema,
|
||||
IngestorOption,
|
||||
} from '../upload/types/ingestor';
|
||||
import { FormField, IngestorConfig, IngestorType } from './types/ingestor';
|
||||
|
||||
import {FilePicker} from '../components/FilePicker';
|
||||
import { FilePicker } from '../components/FilePicker';
|
||||
import GoogleDrivePicker from '../components/GoogleDrivePicker';
|
||||
|
||||
import ChevronRight from '../assets/chevron-right.svg';
|
||||
@@ -46,7 +47,7 @@ function Upload({
|
||||
onSuccessfulUpload?: () => void;
|
||||
}) {
|
||||
const token = useSelector(selectToken);
|
||||
|
||||
|
||||
const [files, setfiles] = useState<File[]>(receivedFile);
|
||||
const [activeTab, setActiveTab] = useState<boolean>(true);
|
||||
const [showAdvancedOptions, setShowAdvancedOptions] = useState(false);
|
||||
@@ -55,9 +56,6 @@ function Upload({
|
||||
const [selectedFiles, setSelectedFiles] = useState<string[]>([]);
|
||||
const [selectedFolders, setSelectedFolders] = useState<string[]>([]);
|
||||
|
||||
|
||||
|
||||
|
||||
const renderFormFields = () => {
|
||||
if (!ingestor.type) return null;
|
||||
const ingestorSchema = getIngestorSchema(ingestor.type as IngestorType);
|
||||
@@ -190,7 +188,7 @@ function Upload({
|
||||
return (
|
||||
<div key={field.name}>
|
||||
<div className="mb-3" {...getRootProps()}>
|
||||
<span className="inline-block text-purple-30 dark:text-silver rounded-3xl border border-[#7F7F82] bg-transparent px-4 py-2 font-medium hover:cursor-pointer">
|
||||
<span className="text-purple-30 dark:text-silver inline-block rounded-3xl border border-[#7F7F82] bg-transparent px-4 py-2 font-medium hover:cursor-pointer">
|
||||
<input type="button" {...getInputProps()} />
|
||||
Choose Files
|
||||
</span>
|
||||
@@ -203,7 +201,7 @@ function Upload({
|
||||
{files.map((file) => (
|
||||
<p
|
||||
key={file.name}
|
||||
className="text-gray-6000 dark:text-[#ececf1] truncate overflow-hidden text-ellipsis"
|
||||
className="text-gray-6000 truncate overflow-hidden text-ellipsis dark:text-[#ececf1]"
|
||||
title={file.name}
|
||||
>
|
||||
{file.name}
|
||||
@@ -222,7 +220,10 @@ function Upload({
|
||||
return (
|
||||
<FilePicker
|
||||
key={field.name}
|
||||
onSelectionChange={(selectedFileIds: string[], selectedFolderIds: string[] = []) => {
|
||||
onSelectionChange={(
|
||||
selectedFileIds: string[],
|
||||
selectedFolderIds: string[] = [],
|
||||
) => {
|
||||
setSelectedFiles(selectedFileIds);
|
||||
setSelectedFolders(selectedFolderIds);
|
||||
}}
|
||||
@@ -236,7 +237,10 @@ function Upload({
|
||||
return (
|
||||
<GoogleDrivePicker
|
||||
key={field.name}
|
||||
onSelectionChange={(selectedFileIds: string[], selectedFolderIds: string[] = []) => {
|
||||
onSelectionChange={(
|
||||
selectedFileIds: string[],
|
||||
selectedFolderIds: string[] = [],
|
||||
) => {
|
||||
setSelectedFiles(selectedFileIds);
|
||||
setSelectedFolders(selectedFolderIds);
|
||||
}}
|
||||
@@ -265,14 +269,14 @@ function Upload({
|
||||
const { t } = useTranslation();
|
||||
const setTimeoutRef = useRef<number | null>(null);
|
||||
|
||||
const ingestorOptions: IngestorOption[] = IngestorFormSchemas
|
||||
.filter(schema => schema.validate ? schema.validate() : true)
|
||||
.map(schema => ({
|
||||
label: schema.label,
|
||||
value: schema.key,
|
||||
icon: schema.icon,
|
||||
heading: schema.heading
|
||||
}));
|
||||
const ingestorOptions: IngestorOption[] = IngestorFormSchemas.filter(
|
||||
(schema) => (schema.validate ? schema.validate() : true),
|
||||
).map((schema) => ({
|
||||
label: schema.label,
|
||||
value: schema.key,
|
||||
icon: schema.icon,
|
||||
heading: schema.heading,
|
||||
}));
|
||||
|
||||
const sourceDocs = useSelector(selectSourceDocs);
|
||||
useEffect(() => {
|
||||
@@ -383,7 +387,8 @@ function Upload({
|
||||
data?.find(
|
||||
(d: Doc) => d.type?.toLowerCase() === 'local',
|
||||
),
|
||||
));
|
||||
),
|
||||
);
|
||||
});
|
||||
setProgress(
|
||||
(progress) =>
|
||||
@@ -457,21 +462,24 @@ function Upload({
|
||||
);
|
||||
}
|
||||
|
||||
const onDrop = useCallback((acceptedFiles: File[]) => {
|
||||
setfiles(acceptedFiles);
|
||||
setIngestor(prev => ({ ...prev, name: acceptedFiles[0]?.name || '' }));
|
||||
const onDrop = useCallback(
|
||||
(acceptedFiles: File[]) => {
|
||||
setfiles(acceptedFiles);
|
||||
setIngestor((prev) => ({ ...prev, name: acceptedFiles[0]?.name || '' }));
|
||||
|
||||
// If we're in local_file mode, update the ingestor config
|
||||
if (ingestor.type === 'local_file') {
|
||||
setIngestor((prevState) => ({
|
||||
...prevState,
|
||||
config: {
|
||||
...prevState.config,
|
||||
files: acceptedFiles,
|
||||
},
|
||||
}));
|
||||
}
|
||||
}, [ingestor.type]);
|
||||
// If we're in local_file mode, update the ingestor config
|
||||
if (ingestor.type === 'local_file') {
|
||||
setIngestor((prevState) => ({
|
||||
...prevState,
|
||||
config: {
|
||||
...prevState.config,
|
||||
files: acceptedFiles,
|
||||
},
|
||||
}));
|
||||
}
|
||||
},
|
||||
[ingestor.type],
|
||||
);
|
||||
|
||||
const doNothing = () => undefined;
|
||||
|
||||
@@ -512,9 +520,15 @@ function Upload({
|
||||
const ingestorSchema = getIngestorSchema(ingestor.type as IngestorType);
|
||||
if (!ingestorSchema) return;
|
||||
const schema: FormField[] = ingestorSchema.fields;
|
||||
const hasLocalFilePicker = schema.some((field: FormField) => field.type === 'local_file_picker');
|
||||
const hasRemoteFilePicker = schema.some((field: FormField) => field.type === 'remote_file_picker');
|
||||
const hasGoogleDrivePicker = schema.some((field: FormField) => field.type === 'google_drive_picker');
|
||||
const hasLocalFilePicker = schema.some(
|
||||
(field: FormField) => field.type === 'local_file_picker',
|
||||
);
|
||||
const hasRemoteFilePicker = schema.some(
|
||||
(field: FormField) => field.type === 'remote_file_picker',
|
||||
);
|
||||
const hasGoogleDrivePicker = schema.some(
|
||||
(field: FormField) => field.type === 'google_drive_picker',
|
||||
);
|
||||
|
||||
if (hasLocalFilePicker) {
|
||||
files.forEach((file) => {
|
||||
@@ -557,15 +571,16 @@ function Upload({
|
||||
}, 3000);
|
||||
};
|
||||
|
||||
const endpoint = ingestor.type === 'local_file' ? `${apiHost}/api/upload` : `${apiHost}/api/remote`;
|
||||
const endpoint =
|
||||
ingestor.type === 'local_file'
|
||||
? `${apiHost}/api/upload`
|
||||
: `${apiHost}/api/remote`;
|
||||
|
||||
xhr.open('POST', endpoint);
|
||||
xhr.setRequestHeader('Authorization', `Bearer ${token}`);
|
||||
xhr.send(formData);
|
||||
};
|
||||
|
||||
|
||||
|
||||
const { getRootProps, getInputProps } = useDropzone({
|
||||
onDrop,
|
||||
multiple: true,
|
||||
@@ -604,12 +619,20 @@ function Upload({
|
||||
}
|
||||
|
||||
if (!ingestor.type) return true;
|
||||
const ingestorSchemaForValidation = getIngestorSchema(ingestor.type as IngestorType);
|
||||
const ingestorSchemaForValidation = getIngestorSchema(
|
||||
ingestor.type as IngestorType,
|
||||
);
|
||||
if (!ingestorSchemaForValidation) return true;
|
||||
const schema: FormField[] = ingestorSchemaForValidation.fields;
|
||||
const hasLocalFilePicker = schema.some((field: FormField) => field.type === 'local_file_picker');
|
||||
const hasRemoteFilePicker = schema.some((field: FormField) => field.type === 'remote_file_picker');
|
||||
const hasGoogleDrivePicker = schema.some((field: FormField) => field.type === 'google_drive_picker');
|
||||
const hasLocalFilePicker = schema.some(
|
||||
(field: FormField) => field.type === 'local_file_picker',
|
||||
);
|
||||
const hasRemoteFilePicker = schema.some(
|
||||
(field: FormField) => field.type === 'remote_file_picker',
|
||||
);
|
||||
const hasGoogleDrivePicker = schema.some(
|
||||
(field: FormField) => field.type === 'google_drive_picker',
|
||||
);
|
||||
|
||||
if (hasLocalFilePicker) {
|
||||
if (files.length === 0) {
|
||||
@@ -621,7 +644,9 @@ function Upload({
|
||||
}
|
||||
}
|
||||
|
||||
const ingestorSchemaForFields = getIngestorSchema(ingestor.type as IngestorType);
|
||||
const ingestorSchemaForFields = getIngestorSchema(
|
||||
ingestor.type as IngestorType,
|
||||
);
|
||||
if (!ingestorSchemaForFields) return false;
|
||||
const formFields: FormField[] = ingestorSchemaForFields.fields;
|
||||
for (const field of formFields) {
|
||||
@@ -686,26 +711,28 @@ function Upload({
|
||||
|
||||
const renderIngestorSelection = () => {
|
||||
return (
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 gap-4 w-full">
|
||||
<div className="grid w-full grid-cols-1 gap-4 sm:grid-cols-2 md:grid-cols-3">
|
||||
{ingestorOptions.map((option) => (
|
||||
<div
|
||||
key={option.value}
|
||||
className={`relative flex flex-col justify-between rounded-2xl cursor-pointer w-full h-[91.2px] border border-solid pt-[21.1px] pr-[21px] pb-[15px] pl-[21px] gap-2 transition-colors duration-300 ease-out mx-auto ${
|
||||
ingestor.type === option.value
|
||||
? 'bg-[#7D54D1] text-white border-[#7D54D1]'
|
||||
: 'bg-transparent hover:bg-[#ECECEC]/30 dark:hover:bg-[#383838]/30 border-[#D7D7D7] dark:border-[#4A4A4A] hover:shadow-[0_0_15px_0_#00000026] transition-shadow duration-300'
|
||||
className={`relative mx-auto flex h-[91.2px] w-full cursor-pointer flex-col justify-between gap-2 rounded-2xl border border-solid pt-[21.1px] pr-[21px] pb-[15px] pl-[21px] transition-colors duration-300 ease-out ${
|
||||
ingestor.type === option.value
|
||||
? 'border-[#7D54D1] bg-[#7D54D1] text-white'
|
||||
: 'border-[#D7D7D7] bg-transparent transition-shadow duration-300 hover:bg-[#ECECEC]/30 hover:shadow-[0_0_15px_0_#00000026] dark:border-[#4A4A4A] dark:hover:bg-[#383838]/30'
|
||||
}`}
|
||||
onClick={() => handleIngestorTypeChange(option.value as IngestorType)}
|
||||
onClick={() =>
|
||||
handleIngestorTypeChange(option.value as IngestorType)
|
||||
}
|
||||
>
|
||||
<div className="flex flex-col justify-between h-full">
|
||||
<div className="w-6 h-6">
|
||||
<img
|
||||
src={option.icon}
|
||||
alt={option.label}
|
||||
className={`${ingestor.type === option.value ? 'filter invert' : ''} dark:filter dark:invert`}
|
||||
<div className="flex h-full flex-col justify-between">
|
||||
<div className="h-6 w-6">
|
||||
<img
|
||||
src={option.icon}
|
||||
alt={option.label}
|
||||
className={`${ingestor.type === option.value ? 'invert filter' : ''} dark:invert dark:filter`}
|
||||
/>
|
||||
</div>
|
||||
<p className="font-inter font-semibold text-[13px] leading-[18px] self-start">
|
||||
<p className="font-inter self-start text-[13px] leading-[18px] font-semibold">
|
||||
{option.label}
|
||||
</p>
|
||||
</div>
|
||||
@@ -720,15 +747,15 @@ function Upload({
|
||||
view = <UploadProgress></UploadProgress>;
|
||||
} else if (progress?.type === 'TRAINING') {
|
||||
view = <TrainingProgress></TrainingProgress>;
|
||||
} else {
|
||||
} else {
|
||||
view = (
|
||||
<div className="flex w-full flex-col gap-6">
|
||||
{!ingestor.type && (
|
||||
<p className="text-[#18181B] dark:text-[#ECECF1] text-left font-inter font-semibold text-[20px] leading-[28px] tracking-[0.15px]">
|
||||
<p className="font-inter text-left text-[20px] leading-[28px] font-semibold tracking-[0.15px] text-[#18181B] dark:text-[#ECECF1]">
|
||||
Select the way to add your source
|
||||
</p>
|
||||
)}
|
||||
|
||||
|
||||
{activeTab && (
|
||||
<>
|
||||
{!ingestor.type && renderIngestorSelection()}
|
||||
@@ -736,18 +763,19 @@ function Upload({
|
||||
<div className="flex flex-col gap-4">
|
||||
<button
|
||||
onClick={() => handleIngestorTypeChange(null)}
|
||||
className="flex items-center gap-2 text-[#777777] hover:text-[#555555] w-fit"
|
||||
className="flex w-fit items-center gap-2 text-[#777777] hover:text-[#555555]"
|
||||
>
|
||||
<img
|
||||
src={ChevronRight}
|
||||
alt="back"
|
||||
className="h-3 w-3 transform rotate-180"
|
||||
<img
|
||||
src={ChevronRight}
|
||||
alt="back"
|
||||
className="h-3 w-3 rotate-180 transform"
|
||||
/>
|
||||
<span>Back</span>
|
||||
</button>
|
||||
|
||||
<h2 className="font-inter font-semibold text-[22px] leading-[28px] tracking-[0.15px] text-black dark:text-[#E0E0E0]">
|
||||
{ingestor.type && getIngestorSchema(ingestor.type as IngestorType)?.heading}
|
||||
<h2 className="font-inter text-[22px] leading-[28px] font-semibold tracking-[0.15px] text-black dark:text-[#E0E0E0]">
|
||||
{ingestor.type &&
|
||||
getIngestorSchema(ingestor.type as IngestorType)?.heading}
|
||||
</h2>
|
||||
|
||||
<Input
|
||||
@@ -769,19 +797,20 @@ function Upload({
|
||||
{renderFormFields()}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{ingestor.type && getIngestorSchema(ingestor.type as IngestorType)?.fields.some(
|
||||
(field: FormField) => field.advanced,
|
||||
) && (
|
||||
<button
|
||||
onClick={() => setShowAdvancedOptions(!showAdvancedOptions)}
|
||||
className="text-purple-30 bg-transparent py-2 pl-0 text-left text-sm font-normal hover:cursor-pointer"
|
||||
>
|
||||
{showAdvancedOptions
|
||||
? t('modals.uploadDoc.hideAdvanced')
|
||||
: t('modals.uploadDoc.showAdvanced')}
|
||||
</button>
|
||||
)}
|
||||
|
||||
{ingestor.type &&
|
||||
getIngestorSchema(ingestor.type as IngestorType)?.fields.some(
|
||||
(field: FormField) => field.advanced,
|
||||
) && (
|
||||
<button
|
||||
onClick={() => setShowAdvancedOptions(!showAdvancedOptions)}
|
||||
className="text-purple-30 bg-transparent py-2 pl-0 text-left text-sm font-normal hover:cursor-pointer"
|
||||
>
|
||||
{showAdvancedOptions
|
||||
? t('modals.uploadDoc.hideAdvanced')
|
||||
: t('modals.uploadDoc.showAdvanced')}
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<div className="flex justify-end gap-4">
|
||||
@@ -789,10 +818,14 @@ function Upload({
|
||||
<button
|
||||
onClick={() => {
|
||||
if (!ingestor.type) return;
|
||||
const ingestorSchemaForUpload = getIngestorSchema(ingestor.type as IngestorType);
|
||||
const ingestorSchemaForUpload = getIngestorSchema(
|
||||
ingestor.type as IngestorType,
|
||||
);
|
||||
if (!ingestorSchemaForUpload) return;
|
||||
const schema: FormField[] = ingestorSchemaForUpload.fields;
|
||||
const hasLocalFilePicker = schema.some((field: FormField) => field.type === 'local_file_picker');
|
||||
const hasLocalFilePicker = schema.some(
|
||||
(field: FormField) => field.type === 'local_file_picker',
|
||||
);
|
||||
|
||||
if (hasLocalFilePicker) {
|
||||
uploadFile();
|
||||
@@ -815,10 +848,6 @@ function Upload({
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return (
|
||||
<WrapperModal
|
||||
isPerformingTask={progress !== undefined && progress.percentage < 100}
|
||||
@@ -828,7 +857,7 @@ function Upload({
|
||||
setfiles([]);
|
||||
setModalState('INACTIVE');
|
||||
}}
|
||||
className="w-11/12 sm:w-auto sm:min-w-[600px] md:min-w-[700px] max-h-[90vh] sm:max-h-none"
|
||||
className="max-h-[90vh] w-11/12 sm:max-h-none sm:w-auto sm:min-w-[600px] md:min-w-[700px]"
|
||||
contentClassName="max-h-[80vh] sm:max-h-none"
|
||||
>
|
||||
{view}
|
||||
|
||||
@@ -5,7 +5,13 @@ import GithubIcon from '../../assets/github.svg';
|
||||
import RedditIcon from '../../assets/reddit.svg';
|
||||
import DriveIcon from '../../assets/drive.svg';
|
||||
|
||||
export type IngestorType = 'crawler' | 'github' | 'reddit' | 'url' | 'google_drive' | 'local_file';
|
||||
export type IngestorType =
|
||||
| 'crawler'
|
||||
| 'github'
|
||||
| 'reddit'
|
||||
| 'url'
|
||||
| 'google_drive'
|
||||
| 'local_file';
|
||||
|
||||
export interface IngestorConfig {
|
||||
type: IngestorType | null;
|
||||
@@ -20,7 +26,14 @@ export type IngestorFormData = {
|
||||
data: string;
|
||||
};
|
||||
|
||||
export type FieldType = 'string' | 'number' | 'enum' | 'boolean' | 'local_file_picker' | 'remote_file_picker' | 'google_drive_picker';
|
||||
export type FieldType =
|
||||
| 'string'
|
||||
| 'number'
|
||||
| 'enum'
|
||||
| 'boolean'
|
||||
| 'local_file_picker'
|
||||
| 'remote_file_picker'
|
||||
| 'google_drive_picker';
|
||||
|
||||
export interface FormField {
|
||||
name: string;
|
||||
@@ -47,29 +60,41 @@ export const IngestorFormSchemas: IngestorSchema[] = [
|
||||
icon: FileUploadIcon,
|
||||
heading: 'Upload new document',
|
||||
fields: [
|
||||
{ name: 'files', label: 'Select files', type: 'local_file_picker', required: true },
|
||||
]
|
||||
{
|
||||
name: 'files',
|
||||
label: 'Select files',
|
||||
type: 'local_file_picker',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'crawler',
|
||||
label: 'Crawler',
|
||||
icon: CrawlerIcon,
|
||||
heading: 'Add content with Web Crawler',
|
||||
fields: [{ name: 'url', label: 'URL', type: 'string', required: true }]
|
||||
fields: [{ name: 'url', label: 'URL', type: 'string', required: true }],
|
||||
},
|
||||
{
|
||||
key: 'url',
|
||||
label: 'Link',
|
||||
icon: UrlIcon,
|
||||
heading: 'Add content from URL',
|
||||
fields: [{ name: 'url', label: 'URL', type: 'string', required: true }]
|
||||
fields: [{ name: 'url', label: 'URL', type: 'string', required: true }],
|
||||
},
|
||||
{
|
||||
key: 'github',
|
||||
label: 'GitHub',
|
||||
icon: GithubIcon,
|
||||
heading: 'Add content from GitHub',
|
||||
fields: [{ name: 'repo_url', label: 'Repository URL', type: 'string', required: true }]
|
||||
fields: [
|
||||
{
|
||||
name: 'repo_url',
|
||||
label: 'Repository URL',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'reddit',
|
||||
@@ -78,11 +103,31 @@ export const IngestorFormSchemas: IngestorSchema[] = [
|
||||
heading: 'Add content from Reddit',
|
||||
fields: [
|
||||
{ name: 'client_id', label: 'Client ID', type: 'string', required: true },
|
||||
{ name: 'client_secret', label: 'Client Secret', type: 'string', required: true },
|
||||
{ name: 'user_agent', label: 'User Agent', type: 'string', required: true },
|
||||
{ name: 'search_queries', label: 'Search Queries', type: 'string', required: true },
|
||||
{ name: 'number_posts', label: 'Number of Posts', type: 'number', required: true },
|
||||
]
|
||||
{
|
||||
name: 'client_secret',
|
||||
label: 'Client Secret',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'user_agent',
|
||||
label: 'User Agent',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'search_queries',
|
||||
label: 'Search Queries',
|
||||
type: 'string',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'number_posts',
|
||||
label: 'Number of Posts',
|
||||
type: 'number',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
key: 'google_drive',
|
||||
@@ -91,7 +136,7 @@ export const IngestorFormSchemas: IngestorSchema[] = [
|
||||
heading: 'Upload from Google Drive',
|
||||
validate: () => {
|
||||
const googleClientId = import.meta.env.VITE_GOOGLE_CLIENT_ID;
|
||||
return !!(googleClientId);
|
||||
return !!googleClientId;
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
@@ -99,12 +144,15 @@ export const IngestorFormSchemas: IngestorSchema[] = [
|
||||
label: 'Select Files from Google Drive',
|
||||
type: 'google_drive_picker',
|
||||
required: true,
|
||||
}
|
||||
]
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const IngestorDefaultConfigs: Record<IngestorType, Omit<IngestorConfig, 'type'>> = {
|
||||
export const IngestorDefaultConfigs: Record<
|
||||
IngestorType,
|
||||
Omit<IngestorConfig, 'type'>
|
||||
> = {
|
||||
crawler: { name: '', config: { url: '' } },
|
||||
url: { name: '', config: { url: '' } },
|
||||
reddit: {
|
||||
@@ -114,8 +162,8 @@ export const IngestorDefaultConfigs: Record<IngestorType, Omit<IngestorConfig, '
|
||||
client_secret: '',
|
||||
user_agent: '',
|
||||
search_queries: '',
|
||||
number_posts: 10
|
||||
}
|
||||
number_posts: 10,
|
||||
},
|
||||
},
|
||||
github: { name: '', config: { repo_url: '' } },
|
||||
google_drive: {
|
||||
@@ -123,8 +171,8 @@ export const IngestorDefaultConfigs: Record<IngestorType, Omit<IngestorConfig, '
|
||||
config: {
|
||||
file_ids: '',
|
||||
folder_ids: '',
|
||||
recursive: true
|
||||
}
|
||||
recursive: true,
|
||||
},
|
||||
},
|
||||
local_file: { name: '', config: { files: [] } },
|
||||
};
|
||||
@@ -136,8 +184,8 @@ export interface IngestorOption {
|
||||
heading: string;
|
||||
}
|
||||
|
||||
export const getIngestorSchema = (key: IngestorType): IngestorSchema | undefined => {
|
||||
return IngestorFormSchemas.find(schema => schema.key === key);
|
||||
export const getIngestorSchema = (
|
||||
key: IngestorType,
|
||||
): IngestorSchema | undefined => {
|
||||
return IngestorFormSchemas.find((schema) => schema.key === key);
|
||||
};
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user