feat: add status handling and visual indicators for file statuses

This commit is contained in:
Deon Sanchez 2025-09-19 12:18:56 -06:00
parent 2cd286de97
commit 61625a30a8
4 changed files with 119 additions and 6 deletions

View file

@ -42,6 +42,7 @@ export interface File {
owner_email: string;
size: number;
connector_type: string;
status?: "processing" | "active" | "unavailable" | "hidden" | "sync";
chunks: ChunkResult[];
}

View file

@ -33,6 +33,7 @@ import { KnowledgeActionsDropdown } from "@/components/knowledge-actions-dropdow
import { DeleteConfirmationDialog } from "../../../components/confirmation-dialog";
import { useDeleteDocument } from "../api/mutations/useDeleteDocument";
import { toast } from "sonner";
import { StatusBadge } from "@/components/ui/status-badge";
// Function to get the appropriate icon for a connector type
function getSourceIcon(connectorType?: string) {
@ -127,7 +128,7 @@ function SearchPage() {
{
field: "size",
headerName: "Size",
valueFormatter: (params) =>
valueFormatter: params =>
params.value ? `${Math.round(params.value / 1024)} KB` : "-",
},
{
@ -137,7 +138,7 @@ function SearchPage() {
{
field: "owner",
headerName: "Owner",
valueFormatter: (params) =>
valueFormatter: params =>
params.data?.owner_name || params.data?.owner_email || "—",
},
{
@ -155,6 +156,15 @@ function SearchPage() {
);
},
},
{
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 || ""} />;
@ -195,7 +205,7 @@ function SearchPage() {
try {
// Delete each file individually since the API expects one filename at a time
const deletePromises = selectedRows.map((row) =>
const deletePromises = selectedRows.map(row =>
deleteDocumentMutation.mutateAsync({ filename: row.filename })
);
@ -252,7 +262,7 @@ function SearchPage() {
type="text"
defaultValue={parsedFilterData?.query}
value={queryInputText}
onChange={(e) => setQueryInputText(e.target.value)}
onChange={e => setQueryInputText(e.target.value)}
placeholder="Search your documents..."
className="flex-1 bg-muted/20 rounded-lg border border-border/50 px-4 py-3 focus-visible:ring-1 focus-visible:ring-ring"
/>
@ -297,7 +307,7 @@ function SearchPage() {
rowSelection="multiple"
rowMultiSelectWithClick={false}
suppressRowClickSelection={true}
getRowId={(params) => params.data.filename}
getRowId={params => params.data.filename}
onSelectionChanged={onSelectionChanged}
suppressHorizontalScroll={false}
noRowsOverlayComponent={() => (
@ -326,7 +336,7 @@ function SearchPage() {
}? 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")}`}
${selectedRows.map(row => `${row.filename}`).join("\n")}`}
confirmText="Delete All"
onConfirm={handleBulkDelete}
isLoading={deleteDocumentMutation.isPending}

View file

@ -0,0 +1,49 @@
interface AnimatedProcessingIconProps {
className?: string;
size?: number;
}
export const AnimatedProcessingIcon = ({
className = "",
size = 10,
}: AnimatedProcessingIconProps) => {
const width = Math.round((size * 6) / 10);
const height = size;
return (
<svg
width={width}
height={height}
viewBox="0 0 6 10"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<style>
{`
.dot-1 { animation: pulse-wave 1.5s infinite; animation-delay: 0s; }
.dot-2 { animation: pulse-wave 1.5s infinite; animation-delay: 0.1s; }
.dot-3 { animation: pulse-wave 1.5s infinite; animation-delay: 0.2s; }
.dot-4 { animation: pulse-wave 1.5s infinite; animation-delay: 0.3s; }
.dot-5 { animation: pulse-wave 1.5s infinite; animation-delay: 0.4s; }
@keyframes pulse-wave {
0%, 60%, 100% {
opacity: 0.25;
transform: scale(1);
}
30% {
opacity: 1;
transform: scale(1.2);
}
}
`}
</style>
<circle className="dot-1" cx="1" cy="5" r="1" fill="currentColor" />
<circle className="dot-2" cx="1" cy="9" r="1" fill="currentColor" />
<circle className="dot-3" cx="5" cy="1" r="1" fill="currentColor" />
<circle className="dot-4" cx="5" cy="5" r="1" fill="currentColor" />
<circle className="dot-5" cx="5" cy="9" r="1" fill="currentColor" />
</svg>
);
};

View file

@ -0,0 +1,53 @@
import { AnimatedProcessingIcon } from "./animated-processing-icon";
export type Status =
| "processing"
| "active"
| "unavailable"
| "hidden"
| "sync";
interface StatusBadgeProps {
status: Status;
className?: string;
}
const statusConfig = {
processing: {
label: "Processing",
className: "text-muted-foreground dark:text-muted-foreground ",
},
active: {
label: "Active",
className: "text-emerald-600 dark:text-emerald-400 ",
},
unavailable: {
label: "Unavailable",
className: "text-red-600 dark:text-red-400 ",
},
hidden: {
label: "Hidden",
className: "text-zinc-400 dark:text-zinc-500 ",
},
sync: {
label: "Sync",
className: "text-amber-700 dark:text-amber-300 underline",
},
};
export const StatusBadge = ({ status, className }: StatusBadgeProps) => {
const config = statusConfig[status];
return (
<div
className={`inline-flex items-center gap-1 ${config.className} ${
className || ""
}`}
>
{status === "processing" && (
<AnimatedProcessingIcon className="text-current mr-2" size={10} />
)}
{config.label}
</div>
);
};