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;
|
owner_email: string;
|
||||||
size: number;
|
size: number;
|
||||||
connector_type: string;
|
connector_type: string;
|
||||||
|
status?: "processing" | "active" | "unavailable" | "hidden" | "sync";
|
||||||
chunks: ChunkResult[];
|
chunks: ChunkResult[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ import { KnowledgeActionsDropdown } from "@/components/knowledge-actions-dropdow
|
||||||
import { DeleteConfirmationDialog } from "../../../components/confirmation-dialog";
|
import { DeleteConfirmationDialog } from "../../../components/confirmation-dialog";
|
||||||
import { useDeleteDocument } from "../api/mutations/useDeleteDocument";
|
import { useDeleteDocument } from "../api/mutations/useDeleteDocument";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { StatusBadge } from "@/components/ui/status-badge";
|
||||||
|
|
||||||
// Function to get the appropriate icon for a connector type
|
// Function to get the appropriate icon for a connector type
|
||||||
function getSourceIcon(connectorType?: string) {
|
function getSourceIcon(connectorType?: string) {
|
||||||
|
|
@ -127,7 +128,7 @@ function SearchPage() {
|
||||||
{
|
{
|
||||||
field: "size",
|
field: "size",
|
||||||
headerName: "Size",
|
headerName: "Size",
|
||||||
valueFormatter: (params) =>
|
valueFormatter: params =>
|
||||||
params.value ? `${Math.round(params.value / 1024)} KB` : "-",
|
params.value ? `${Math.round(params.value / 1024)} KB` : "-",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -137,7 +138,7 @@ function SearchPage() {
|
||||||
{
|
{
|
||||||
field: "owner",
|
field: "owner",
|
||||||
headerName: "Owner",
|
headerName: "Owner",
|
||||||
valueFormatter: (params) =>
|
valueFormatter: params =>
|
||||||
params.data?.owner_name || params.data?.owner_email || "—",
|
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>) => {
|
cellRenderer: ({ data }: CustomCellRendererProps<File>) => {
|
||||||
return <KnowledgeActionsDropdown filename={data?.filename || ""} />;
|
return <KnowledgeActionsDropdown filename={data?.filename || ""} />;
|
||||||
|
|
@ -195,7 +205,7 @@ function SearchPage() {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Delete each file individually since the API expects one filename at a time
|
// 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 })
|
deleteDocumentMutation.mutateAsync({ filename: row.filename })
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -252,7 +262,7 @@ function SearchPage() {
|
||||||
type="text"
|
type="text"
|
||||||
defaultValue={parsedFilterData?.query}
|
defaultValue={parsedFilterData?.query}
|
||||||
value={queryInputText}
|
value={queryInputText}
|
||||||
onChange={(e) => setQueryInputText(e.target.value)}
|
onChange={e => setQueryInputText(e.target.value)}
|
||||||
placeholder="Search your documents..."
|
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"
|
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"
|
rowSelection="multiple"
|
||||||
rowMultiSelectWithClick={false}
|
rowMultiSelectWithClick={false}
|
||||||
suppressRowClickSelection={true}
|
suppressRowClickSelection={true}
|
||||||
getRowId={(params) => params.data.filename}
|
getRowId={params => params.data.filename}
|
||||||
onSelectionChanged={onSelectionChanged}
|
onSelectionChanged={onSelectionChanged}
|
||||||
suppressHorizontalScroll={false}
|
suppressHorizontalScroll={false}
|
||||||
noRowsOverlayComponent={() => (
|
noRowsOverlayComponent={() => (
|
||||||
|
|
@ -326,7 +336,7 @@ function SearchPage() {
|
||||||
}? This will remove all chunks and data associated with these documents. This action cannot be undone.
|
}? This will remove all chunks and data associated with these documents. This action cannot be undone.
|
||||||
|
|
||||||
Documents to be deleted:
|
Documents to be deleted:
|
||||||
${selectedRows.map((row) => `• ${row.filename}`).join("\n")}`}
|
${selectedRows.map(row => `• ${row.filename}`).join("\n")}`}
|
||||||
confirmText="Delete All"
|
confirmText="Delete All"
|
||||||
onConfirm={handleBulkDelete}
|
onConfirm={handleBulkDelete}
|
||||||
isLoading={deleteDocumentMutation.isPending}
|
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