Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 67 additions & 19 deletions frontend/src/components/CollectionNavigation.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import React from "react"
import { ChevronRight } from "lucide-react"
import { ChevronRight, AlertOctagon } from "lucide-react"
import { DropdownMenuItem } from "@/components/ui/dropdown-menu"
import { api } from "@/api"
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "@/components/ui/tooltip"

interface CollectionNavigationProps {
navigationPath: Array<{
Expand Down Expand Up @@ -49,6 +55,12 @@ interface CollectionNavigationProps {
navigateToCl: (clId: string, clName: string) => Promise<void>
}

// Helper function to check if an item should be non-selectable based on upload status
function isItemNonSelectable(item: any): boolean {
const uploadStatus = item.uploadStatus
return uploadStatus === "pending" || uploadStatus === "processing" || uploadStatus === "failed"
}

// Utility function to check if an item is selected either directly or through parent inheritance
function isItemSelectedWithInheritance(
item: any,
Expand Down Expand Up @@ -198,10 +210,14 @@ export const CollectionNavigation: React.FC<CollectionNavigationProps> = ({
)

const isInherited = isSelected && !isDirectlySelected
const isNonSelectable = isItemNonSelectable(result)

const handleResultSelect = () => {
// Don't allow selection changes for inherited items
if (isInherited) return

// For non-selectable items, prevent all interactions (no navigation or selection)
if (isNonSelectable) return

if (result.type === "collection") {
// Toggle collection selection
Expand Down Expand Up @@ -262,25 +278,25 @@ export const CollectionNavigation: React.FC<CollectionNavigationProps> = ({
return (
<div
key={result.id}
onClick={isInherited ? undefined : handleResultSelect}
onClick={isInherited || isNonSelectable ? undefined : handleResultSelect}
className={`flex items-center px-4 py-2 text-sm ${
isInherited
? "cursor-default opacity-75"
isInherited || isNonSelectable
? "cursor-not-allowed opacity-50"
: "cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800"
}`}
>
<input
type="checkbox"
checked={isSelected || false}
disabled={isInherited}
disabled={isInherited || isNonSelectable}
onChange={() => {}}
className={`w-4 h-4 mr-3 ${isInherited ? "opacity-60" : ""}`}
className={`w-4 h-4 mr-3 ${isInherited || isNonSelectable ? "opacity-60" : ""}`}
/>
<div className="flex-1 min-w-0">
<div className="flex items-center">
<span className="text-gray-700 dark:text-gray-200 truncate">
{result.name}
</span>
<span className={`truncate ${isNonSelectable ? "text-gray-400 dark:text-gray-500" : "text-gray-700 dark:text-gray-200"}`}>
{result.name}
</span>
<span className="text-xs text-gray-500 dark:text-gray-400 ml-2 px-2 py-1 bg-gray-100 dark:bg-gray-700 rounded">
{result.type}
</span>
Expand All @@ -289,6 +305,18 @@ export const CollectionNavigation: React.FC<CollectionNavigationProps> = ({
Selected
</span>
)}
{isNonSelectable && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<AlertOctagon className="w-4 h-4 ml-2 text-gray-500" />
</TooltipTrigger>
<TooltipContent>
<p>Indexing is in progress</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
</div>
{result.collectionName &&
result.type !== "collection" && (
Expand Down Expand Up @@ -370,13 +398,20 @@ export const CollectionNavigation: React.FC<CollectionNavigationProps> = ({
Loading...
</div>
) : currentItems.length > 0 ? (
currentItems.map((item: any) => (
currentItems.map((item: any) => {
const isNonSelectable = isItemNonSelectable(item)

return (
<div
key={item.id}
className="flex items-center px-4 py-2 text-sm cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800"
className={`flex items-center px-4 py-2 text-sm ${
isNonSelectable
? "cursor-not-allowed opacity-50"
: "cursor-pointer hover:bg-gray-50 dark:hover:bg-gray-800"
}`}
onClick={() => {
if (item.type === "folder") {
// When navigating to a folder, if it's selected, auto-select all children
if (item.type === "folder" && !isNonSelectable) {
// Only allow navigation to folder if it's selectable
navigateToFolder(item.id, item.name)
}
}}
Expand Down Expand Up @@ -425,7 +460,7 @@ export const CollectionNavigation: React.FC<CollectionNavigationProps> = ({
isSelected || isInheritedFromParent,
)
const isDisabled: boolean = Boolean(
isInheritedFromParent && !isSelected,
(isInheritedFromParent && !isSelected) || isNonSelectable,
)

return (
Expand All @@ -435,7 +470,7 @@ export const CollectionNavigation: React.FC<CollectionNavigationProps> = ({
disabled={isDisabled}
onChange={(e) => {
e.stopPropagation()
if (isDisabled) return // Prevent changes if inherited from parent
if (isDisabled) return // Prevent changes if inherited from parent or non-selectable

const isCurrentlySelected = selectedSet.has(item.id)

Expand Down Expand Up @@ -557,19 +592,32 @@ export const CollectionNavigation: React.FC<CollectionNavigationProps> = ({
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="mr-2 text-gray-800"
className={`mr-2 ${isNonSelectable ? "text-gray-400" : "text-gray-800"}`}
>
<path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path>
</svg>
)}
<span className="text-gray-700 dark:text-gray-200 truncate flex-1">
<span className={`truncate flex-1 ${isNonSelectable ? "text-gray-400 dark:text-gray-500" : "text-gray-700 dark:text-gray-200"}`}>
{item.name}
</span>
{item.type === "folder" && (
{isNonSelectable && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<AlertOctagon className="w-4 h-4 ml-2 text-gray-500" />
</TooltipTrigger>
<TooltipContent>
<p>Indexing is in progress</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
{item.type === "folder" && !isNonSelectable && (
<ChevronRight className="h-4 w-4 text-gray-400 ml-2" />
)}
</div>
))
)
})
) : (
<div className="px-4 py-8 text-sm text-gray-500 dark:text-gray-400 text-center">
No items found
Expand Down
80 changes: 80 additions & 0 deletions frontend/src/components/FileTree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import {
Plus,
Download,
Trash2,
Check,
Loader2,
AlertOctagon,
RotateCcw,
} from "lucide-react"
import {
Tooltip,
Expand Down Expand Up @@ -58,6 +62,7 @@ interface FileTreeProps {
onToggle: (node: FileNode) => void
onFileClick: (node: FileNode) => void
onDownload?: (node: FileNode, path: string) => void
onRetry?: (node: FileNode, path: string) => void
}

const FileTree = ({
Expand All @@ -67,6 +72,7 @@ const FileTree = ({
onToggle,
onFileClick,
onDownload,
onRetry,
}: FileTreeProps) => {
return (
<div className="mt-2">
Expand All @@ -79,6 +85,7 @@ const FileTree = ({
onToggle={onToggle}
onFileClick={onFileClick}
onDownload={onDownload}
onRetry={onRetry}
/>
))}
</div>
Expand All @@ -94,6 +101,7 @@ const FileNodeComponent = ({
onToggle,
onFileClick,
onDownload,
onRetry,
}: {
node: FileNode
level?: number
Expand All @@ -103,6 +111,7 @@ const FileNodeComponent = ({
onToggle: (node: FileNode) => void
onFileClick: (node: FileNode) => void
onDownload?: (node: FileNode, path: string) => void
onRetry?: (node: FileNode, path: string) => void
}) => {
const [isHovered, setIsHovered] = useState(false)

Expand Down Expand Up @@ -136,6 +145,31 @@ const FileNodeComponent = ({
>
{node.name}
</span>
{/* Upload status indicator for folders */}
{node.uploadStatus && (
<div className="flex-shrink-0">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div>
{node.uploadStatus === "completed" && (
<Check size={14} className="text-green-600 dark:text-green-400" />
)}
{(node.uploadStatus === "processing" || node.uploadStatus === "pending") && (
<Loader2 size={14} className="text-black dark:text-white animate-spin" />
)}
{node.uploadStatus === "failed" && (
<AlertOctagon size={14} className="text-red-500" />
)}
</div>
</TooltipTrigger>
<TooltipContent>
<p>{node.statusMessage || node.uploadStatus}</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
)}
</div>
) : (
<div
Expand All @@ -149,6 +183,31 @@ const FileNodeComponent = ({
>
{node.name}
</span>
{/* Upload status indicator */}
{node.uploadStatus && (
<div className="flex-shrink-0">
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<div>
{node.uploadStatus === "completed" && (
<Check size={14} className="text-green-600 dark:text-green-400" />
)}
{(node.uploadStatus === "processing" || node.uploadStatus === "pending") && (
<Loader2 size={14} className="text-black dark:text-white animate-spin" />
)}
{node.uploadStatus === "failed" && (
<AlertOctagon size={14} className="text-red-500" />
)}
</div>
</TooltipTrigger>
<TooltipContent>
<p>{node.statusMessage }</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
</div>
)}
</div>
)}
</div>
Expand Down Expand Up @@ -176,6 +235,26 @@ const FileNodeComponent = ({
}}
/>
)}
{(node.retryCount ?? 0) == 4 && onRetry && (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
<RotateCcw
size={16}
className="cursor-pointer text-gray-700 dark:text-gray-200 flex-shrink-0"
onClick={(e) => {
e.preventDefault()
e.stopPropagation()
onRetry(node, currentPath)
}}
/>
</TooltipTrigger>
<TooltipContent>
<p>Retry</p>
</TooltipContent>
</Tooltip>
</TooltipProvider>
)}
<Trash2
size={16}
className="cursor-pointer"
Expand Down Expand Up @@ -238,6 +317,7 @@ const FileNodeComponent = ({
onToggle={onToggle}
onFileClick={onFileClick}
onDownload={onDownload}
onRetry={onRetry}
/>
))}
</div>
Expand Down
Loading