feat: add status handling and visual indicators for file statuses
This commit is contained in:
parent
2cd286de97
commit
61625a30a8
4 changed files with 119 additions and 6 deletions
|
|
@ -42,6 +42,7 @@ export interface File {
|
|||
owner_email: string;
|
||||
size: number;
|
||||
connector_type: string;
|
||||
status?: "processing" | "active" | "unavailable" | "hidden" | "sync";
|
||||
chunks: ChunkResult[];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
49
frontend/src/components/ui/animated-processing-icon.tsx
Normal file
49
frontend/src/components/ui/animated-processing-icon.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
53
frontend/src/components/ui/status-badge.tsx
Normal file
53
frontend/src/components/ui/status-badge.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
Loading…
Add table
Reference in a new issue