format
This commit is contained in:
parent
abb0c9868c
commit
3e10f97659
1 changed files with 267 additions and 267 deletions
|
|
@ -24,230 +24,230 @@ import { useDeleteDocument } from "../api/mutations/useDeleteDocument";
|
|||
|
||||
// Function to get the appropriate icon for a connector type
|
||||
function getSourceIcon(connectorType?: string) {
|
||||
switch (connectorType) {
|
||||
case "google_drive":
|
||||
return (
|
||||
<SiGoogledrive className="h-4 w-4 text-foreground flex-shrink-0" />
|
||||
);
|
||||
case "onedrive":
|
||||
return (
|
||||
<TbBrandOnedrive className="h-4 w-4 text-foreground flex-shrink-0" />
|
||||
);
|
||||
case "sharepoint":
|
||||
return <Building2 className="h-4 w-4 text-foreground flex-shrink-0" />;
|
||||
case "s3":
|
||||
return <Cloud className="h-4 w-4 text-foreground flex-shrink-0" />;
|
||||
default:
|
||||
return (
|
||||
<HardDrive className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
||||
);
|
||||
}
|
||||
switch (connectorType) {
|
||||
case "google_drive":
|
||||
return (
|
||||
<SiGoogledrive className="h-4 w-4 text-foreground flex-shrink-0" />
|
||||
);
|
||||
case "onedrive":
|
||||
return (
|
||||
<TbBrandOnedrive className="h-4 w-4 text-foreground flex-shrink-0" />
|
||||
);
|
||||
case "sharepoint":
|
||||
return <Building2 className="h-4 w-4 text-foreground flex-shrink-0" />;
|
||||
case "s3":
|
||||
return <Cloud className="h-4 w-4 text-foreground flex-shrink-0" />;
|
||||
default:
|
||||
return (
|
||||
<HardDrive className="h-4 w-4 text-muted-foreground flex-shrink-0" />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function SearchPage() {
|
||||
const router = useRouter();
|
||||
const { isMenuOpen } = useTask();
|
||||
const { selectedFilter, setSelectedFilter, parsedFilterData, isPanelOpen } =
|
||||
useKnowledgeFilter();
|
||||
const [selectedRows, setSelectedRows] = useState<File[]>([]);
|
||||
const [showBulkDeleteDialog, setShowBulkDeleteDialog] = useState(false);
|
||||
const router = useRouter();
|
||||
const { isMenuOpen } = useTask();
|
||||
const { selectedFilter, setSelectedFilter, parsedFilterData, isPanelOpen } =
|
||||
useKnowledgeFilter();
|
||||
const [selectedRows, setSelectedRows] = useState<File[]>([]);
|
||||
const [showBulkDeleteDialog, setShowBulkDeleteDialog] = useState(false);
|
||||
|
||||
const deleteDocumentMutation = useDeleteDocument();
|
||||
const deleteDocumentMutation = useDeleteDocument();
|
||||
|
||||
const { data = [], isFetching } = useGetSearchQuery(
|
||||
parsedFilterData?.query || "*",
|
||||
parsedFilterData,
|
||||
);
|
||||
const { data = [], isFetching } = useGetSearchQuery(
|
||||
parsedFilterData?.query || "*",
|
||||
parsedFilterData,
|
||||
);
|
||||
|
||||
const handleTableSearch = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
gridRef.current?.api.setGridOption("quickFilterText", e.target.value);
|
||||
};
|
||||
const handleTableSearch = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
gridRef.current?.api.setGridOption("quickFilterText", e.target.value);
|
||||
};
|
||||
|
||||
const fileResults = data as File[];
|
||||
const fileResults = data as File[];
|
||||
|
||||
const gridRef = useRef<AgGridReact>(null);
|
||||
const gridRef = useRef<AgGridReact>(null);
|
||||
|
||||
const [columnDefs] = useState<ColDef<File>[]>([
|
||||
{
|
||||
field: "filename",
|
||||
headerName: "Source",
|
||||
checkboxSelection: true,
|
||||
headerCheckboxSelection: true,
|
||||
initialFlex: 2,
|
||||
minWidth: 220,
|
||||
cellRenderer: ({ data, value }: CustomCellRendererProps<File>) => {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center gap-2 cursor-pointer hover:text-blue-600 transition-colors"
|
||||
onClick={() => {
|
||||
router.push(
|
||||
`/knowledge/chunks?filename=${encodeURIComponent(
|
||||
data?.filename ?? "",
|
||||
)}`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
{getSourceIcon(data?.connector_type)}
|
||||
<span className="font-medium text-foreground truncate">
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "size",
|
||||
headerName: "Size",
|
||||
valueFormatter: (params) =>
|
||||
params.value ? `${Math.round(params.value / 1024)} KB` : "-",
|
||||
},
|
||||
{
|
||||
field: "mimetype",
|
||||
headerName: "Type",
|
||||
},
|
||||
{
|
||||
field: "owner",
|
||||
headerName: "Owner",
|
||||
valueFormatter: (params) =>
|
||||
params.data?.owner_name || params.data?.owner_email || "—",
|
||||
},
|
||||
{
|
||||
field: "chunkCount",
|
||||
headerName: "Chunks",
|
||||
},
|
||||
{
|
||||
field: "avgScore",
|
||||
headerName: "Avg score",
|
||||
initialFlex: 0.5,
|
||||
cellRenderer: ({ value }: CustomCellRendererProps<File>) => {
|
||||
return (
|
||||
<span className="text-xs text-green-400 bg-green-400/20 px-2 py-1 rounded">
|
||||
{value.toFixed(2)}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
// LOOK HERE LUCAS!!!!
|
||||
// {
|
||||
// field: "status",
|
||||
// headerName: "Status",
|
||||
// cellRenderer: ({ data }: CustomCellRendererProps<File>) => {
|
||||
// // Default to 'active' status if no status is provided
|
||||
// const status = data?.status || "processing";
|
||||
// return <StatusBadge status={status} />;
|
||||
// },
|
||||
// },
|
||||
{
|
||||
cellRenderer: ({ data }: CustomCellRendererProps<File>) => {
|
||||
return <KnowledgeActionsDropdown filename={data?.filename || ""} />;
|
||||
},
|
||||
cellStyle: {
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
padding: 0,
|
||||
},
|
||||
colId: "actions",
|
||||
filter: false,
|
||||
minWidth: 0,
|
||||
width: 40,
|
||||
resizable: false,
|
||||
sortable: false,
|
||||
initialFlex: 0,
|
||||
},
|
||||
]);
|
||||
const [columnDefs] = useState<ColDef<File>[]>([
|
||||
{
|
||||
field: "filename",
|
||||
headerName: "Source",
|
||||
checkboxSelection: true,
|
||||
headerCheckboxSelection: true,
|
||||
initialFlex: 2,
|
||||
minWidth: 220,
|
||||
cellRenderer: ({ data, value }: CustomCellRendererProps<File>) => {
|
||||
return (
|
||||
<div
|
||||
className="flex items-center gap-2 cursor-pointer hover:text-blue-600 transition-colors"
|
||||
onClick={() => {
|
||||
router.push(
|
||||
`/knowledge/chunks?filename=${encodeURIComponent(
|
||||
data?.filename ?? "",
|
||||
)}`,
|
||||
);
|
||||
}}
|
||||
>
|
||||
{getSourceIcon(data?.connector_type)}
|
||||
<span className="font-medium text-foreground truncate">
|
||||
{value}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
field: "size",
|
||||
headerName: "Size",
|
||||
valueFormatter: (params) =>
|
||||
params.value ? `${Math.round(params.value / 1024)} KB` : "-",
|
||||
},
|
||||
{
|
||||
field: "mimetype",
|
||||
headerName: "Type",
|
||||
},
|
||||
{
|
||||
field: "owner",
|
||||
headerName: "Owner",
|
||||
valueFormatter: (params) =>
|
||||
params.data?.owner_name || params.data?.owner_email || "—",
|
||||
},
|
||||
{
|
||||
field: "chunkCount",
|
||||
headerName: "Chunks",
|
||||
},
|
||||
{
|
||||
field: "avgScore",
|
||||
headerName: "Avg score",
|
||||
initialFlex: 0.5,
|
||||
cellRenderer: ({ value }: CustomCellRendererProps<File>) => {
|
||||
return (
|
||||
<span className="text-xs text-green-400 bg-green-400/20 px-2 py-1 rounded">
|
||||
{value.toFixed(2)}
|
||||
</span>
|
||||
);
|
||||
},
|
||||
},
|
||||
// LOOK HERE LUCAS!!!!
|
||||
// {
|
||||
// field: "status",
|
||||
// headerName: "Status",
|
||||
// cellRenderer: ({ data }: CustomCellRendererProps<File>) => {
|
||||
// // Default to 'active' status if no status is provided
|
||||
// const status = data?.status || "processing";
|
||||
// return <StatusBadge status={status} />;
|
||||
// },
|
||||
// },
|
||||
{
|
||||
cellRenderer: ({ data }: CustomCellRendererProps<File>) => {
|
||||
return <KnowledgeActionsDropdown filename={data?.filename || ""} />;
|
||||
},
|
||||
cellStyle: {
|
||||
alignItems: "center",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
padding: 0,
|
||||
},
|
||||
colId: "actions",
|
||||
filter: false,
|
||||
minWidth: 0,
|
||||
width: 40,
|
||||
resizable: false,
|
||||
sortable: false,
|
||||
initialFlex: 0,
|
||||
},
|
||||
]);
|
||||
|
||||
const defaultColDef: ColDef<File> = {
|
||||
resizable: false,
|
||||
suppressMovable: true,
|
||||
initialFlex: 1,
|
||||
minWidth: 100,
|
||||
};
|
||||
const defaultColDef: ColDef<File> = {
|
||||
resizable: false,
|
||||
suppressMovable: true,
|
||||
initialFlex: 1,
|
||||
minWidth: 100,
|
||||
};
|
||||
|
||||
const onSelectionChanged = useCallback(() => {
|
||||
if (gridRef.current) {
|
||||
const selectedNodes = gridRef.current.api.getSelectedRows();
|
||||
setSelectedRows(selectedNodes);
|
||||
}
|
||||
}, []);
|
||||
const onSelectionChanged = useCallback(() => {
|
||||
if (gridRef.current) {
|
||||
const selectedNodes = gridRef.current.api.getSelectedRows();
|
||||
setSelectedRows(selectedNodes);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleBulkDelete = async () => {
|
||||
if (selectedRows.length === 0) return;
|
||||
const handleBulkDelete = async () => {
|
||||
if (selectedRows.length === 0) return;
|
||||
|
||||
try {
|
||||
// Delete each file individually since the API expects one filename at a time
|
||||
const deletePromises = selectedRows.map((row) =>
|
||||
deleteDocumentMutation.mutateAsync({ filename: row.filename }),
|
||||
);
|
||||
try {
|
||||
// Delete each file individually since the API expects one filename at a time
|
||||
const deletePromises = selectedRows.map((row) =>
|
||||
deleteDocumentMutation.mutateAsync({ filename: row.filename }),
|
||||
);
|
||||
|
||||
await Promise.all(deletePromises);
|
||||
await Promise.all(deletePromises);
|
||||
|
||||
toast.success(
|
||||
`Successfully deleted ${selectedRows.length} document${
|
||||
selectedRows.length > 1 ? "s" : ""
|
||||
}`,
|
||||
);
|
||||
setSelectedRows([]);
|
||||
setShowBulkDeleteDialog(false);
|
||||
toast.success(
|
||||
`Successfully deleted ${selectedRows.length} document${
|
||||
selectedRows.length > 1 ? "s" : ""
|
||||
}`,
|
||||
);
|
||||
setSelectedRows([]);
|
||||
setShowBulkDeleteDialog(false);
|
||||
|
||||
// Clear selection in the grid
|
||||
if (gridRef.current) {
|
||||
gridRef.current.api.deselectAll();
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Failed to delete some documents",
|
||||
);
|
||||
}
|
||||
};
|
||||
// Clear selection in the grid
|
||||
if (gridRef.current) {
|
||||
gridRef.current.api.deselectAll();
|
||||
}
|
||||
} catch (error) {
|
||||
toast.error(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: "Failed to delete some documents",
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`fixed inset-0 md:left-72 top-[53px] flex flex-col transition-all duration-300 ${
|
||||
isMenuOpen && isPanelOpen
|
||||
? "md:right-[704px]"
|
||||
: // Both open: 384px (menu) + 320px (KF panel)
|
||||
isMenuOpen
|
||||
? "md:right-96"
|
||||
: // Only menu open: 384px
|
||||
isPanelOpen
|
||||
? "md:right-80"
|
||||
: // Only KF panel open: 320px
|
||||
"md:right-6" // Neither open: 24px
|
||||
}`}
|
||||
>
|
||||
<div className="flex-1 flex flex-col min-h-0 px-6 py-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-lg font-semibold">Project Knowledge</h2>
|
||||
<KnowledgeDropdown variant="button" />
|
||||
</div>
|
||||
return (
|
||||
<div
|
||||
className={`fixed inset-0 md:left-72 top-[53px] flex flex-col transition-all duration-300 ${
|
||||
isMenuOpen && isPanelOpen
|
||||
? "md:right-[704px]"
|
||||
: // Both open: 384px (menu) + 320px (KF panel)
|
||||
isMenuOpen
|
||||
? "md:right-96"
|
||||
: // Only menu open: 384px
|
||||
isPanelOpen
|
||||
? "md:right-80"
|
||||
: // Only KF panel open: 320px
|
||||
"md:right-6" // Neither open: 24px
|
||||
}`}
|
||||
>
|
||||
<div className="flex-1 flex flex-col min-h-0 px-6 py-6">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<h2 className="text-lg font-semibold">Project Knowledge</h2>
|
||||
<KnowledgeDropdown variant="button" />
|
||||
</div>
|
||||
|
||||
{/* Search Input Area */}
|
||||
<div className="flex-shrink-0 mb-6 xl:max-w-[75%]">
|
||||
<form className="flex gap-3">
|
||||
<div className="primary-input min-h-10 !flex items-center flex-nowrap gap-2 focus-within:border-foreground transition-colors !py-0">
|
||||
{selectedFilter?.name && (
|
||||
<div className="flex items-center gap-1 bg-blue-500/20 text-blue-400 px-1.5 py-0.5 rounded max-w-[300px]">
|
||||
<span className="truncate">{selectedFilter?.name}</span>
|
||||
<X
|
||||
aria-label="Remove filter"
|
||||
className="h-4 w-4 flex-shrink-0 cursor-pointer"
|
||||
onClick={() => setSelectedFilter(null)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<input
|
||||
className="bg-transparent w-full h-full focus:outline-none focus-visible:outline-none placeholder:font-mono"
|
||||
name="search-query"
|
||||
id="search-query"
|
||||
type="text"
|
||||
placeholder="Search your documents..."
|
||||
onChange={handleTableSearch}
|
||||
/>
|
||||
</div>
|
||||
{/* <Button
|
||||
{/* Search Input Area */}
|
||||
<div className="flex-shrink-0 mb-6 xl:max-w-[75%]">
|
||||
<form className="flex gap-3">
|
||||
<div className="primary-input min-h-10 !flex items-center flex-nowrap gap-2 focus-within:border-foreground transition-colors !py-0">
|
||||
{selectedFilter?.name && (
|
||||
<div className="flex items-center gap-1 bg-blue-500/20 text-blue-400 px-1.5 py-0.5 rounded max-w-[300px]">
|
||||
<span className="truncate">{selectedFilter?.name}</span>
|
||||
<X
|
||||
aria-label="Remove filter"
|
||||
className="h-4 w-4 flex-shrink-0 cursor-pointer"
|
||||
onClick={() => setSelectedFilter(null)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<input
|
||||
className="bg-transparent w-full h-full focus:outline-none focus-visible:outline-none placeholder:font-mono"
|
||||
name="search-query"
|
||||
id="search-query"
|
||||
type="text"
|
||||
placeholder="Search your documents..."
|
||||
onChange={handleTableSearch}
|
||||
/>
|
||||
</div>
|
||||
{/* <Button
|
||||
type="submit"
|
||||
variant="outline"
|
||||
className="rounded-lg p-0 flex-shrink-0"
|
||||
|
|
@ -258,8 +258,8 @@ function SearchPage() {
|
|||
<Search className="h-4 w-4" />
|
||||
)}
|
||||
</Button> */}
|
||||
{/* //TODO: Implement sync button */}
|
||||
{/* <Button
|
||||
{/* //TODO: Implement sync button */}
|
||||
{/* <Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
className="rounded-lg flex-shrink-0"
|
||||
|
|
@ -267,70 +267,70 @@ function SearchPage() {
|
|||
>
|
||||
Sync
|
||||
</Button> */}
|
||||
{selectedRows.length > 0 && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
className="rounded-lg flex-shrink-0"
|
||||
onClick={() => setShowBulkDeleteDialog(true)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" /> Delete
|
||||
</Button>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
<AgGridReact
|
||||
className="w-full overflow-auto"
|
||||
columnDefs={columnDefs}
|
||||
defaultColDef={defaultColDef}
|
||||
loading={isFetching}
|
||||
ref={gridRef}
|
||||
rowData={fileResults}
|
||||
rowSelection="multiple"
|
||||
rowMultiSelectWithClick={false}
|
||||
suppressRowClickSelection={true}
|
||||
getRowId={(params) => params.data.filename}
|
||||
domLayout="autoHeight"
|
||||
onSelectionChanged={onSelectionChanged}
|
||||
noRowsOverlayComponent={() => (
|
||||
<div className="text-center">
|
||||
<Search className="h-12 w-12 mx-auto mb-4 text-muted-foreground/50" />
|
||||
<p className="text-lg text-muted-foreground">
|
||||
No documents found
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground/70 mt-2">
|
||||
Try adjusting your search terms
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
{selectedRows.length > 0 && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="destructive"
|
||||
className="rounded-lg flex-shrink-0"
|
||||
onClick={() => setShowBulkDeleteDialog(true)}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" /> Delete
|
||||
</Button>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
<AgGridReact
|
||||
className="w-full overflow-auto"
|
||||
columnDefs={columnDefs}
|
||||
defaultColDef={defaultColDef}
|
||||
loading={isFetching}
|
||||
ref={gridRef}
|
||||
rowData={fileResults}
|
||||
rowSelection="multiple"
|
||||
rowMultiSelectWithClick={false}
|
||||
suppressRowClickSelection={true}
|
||||
getRowId={(params) => params.data.filename}
|
||||
domLayout="autoHeight"
|
||||
onSelectionChanged={onSelectionChanged}
|
||||
noRowsOverlayComponent={() => (
|
||||
<div className="text-center">
|
||||
<Search className="h-12 w-12 mx-auto mb-4 text-muted-foreground/50" />
|
||||
<p className="text-lg text-muted-foreground">
|
||||
No documents found
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground/70 mt-2">
|
||||
Try adjusting your search terms
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Bulk Delete Confirmation Dialog */}
|
||||
<DeleteConfirmationDialog
|
||||
open={showBulkDeleteDialog}
|
||||
onOpenChange={setShowBulkDeleteDialog}
|
||||
title="Delete Documents"
|
||||
description={`Are you sure you want to delete ${
|
||||
selectedRows.length
|
||||
} document${
|
||||
selectedRows.length > 1 ? "s" : ""
|
||||
}? This will remove all chunks and data associated with these documents. This action cannot be undone.
|
||||
{/* Bulk Delete Confirmation Dialog */}
|
||||
<DeleteConfirmationDialog
|
||||
open={showBulkDeleteDialog}
|
||||
onOpenChange={setShowBulkDeleteDialog}
|
||||
title="Delete Documents"
|
||||
description={`Are you sure you want to delete ${
|
||||
selectedRows.length
|
||||
} document${
|
||||
selectedRows.length > 1 ? "s" : ""
|
||||
}? This will remove all chunks and data associated with these documents. This action cannot be undone.
|
||||
|
||||
Documents to be deleted:
|
||||
${selectedRows.map((row) => `• ${row.filename}`).join("\n")}`}
|
||||
confirmText="Delete All"
|
||||
onConfirm={handleBulkDelete}
|
||||
isLoading={deleteDocumentMutation.isPending}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
confirmText="Delete All"
|
||||
onConfirm={handleBulkDelete}
|
||||
isLoading={deleteDocumentMutation.isPending}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default function ProtectedSearchPage() {
|
||||
return (
|
||||
<ProtectedRoute>
|
||||
<SearchPage />
|
||||
</ProtectedRoute>
|
||||
);
|
||||
return (
|
||||
<ProtectedRoute>
|
||||
<SearchPage />
|
||||
</ProtectedRoute>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue