|
@@ -3,6 +3,8 @@
|
|
|
import {
|
|
import {
|
|
|
ChevronDownIcon,
|
|
ChevronDownIcon,
|
|
|
ChevronRightIcon,
|
|
ChevronRightIcon,
|
|
|
|
|
+ DocumentDuplicateIcon,
|
|
|
|
|
+ SparklesIcon,
|
|
|
TrashIcon,
|
|
TrashIcon,
|
|
|
} from "@heroicons/react/24/outline";
|
|
} from "@heroicons/react/24/outline";
|
|
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
@@ -250,6 +252,34 @@ export default function FileList() {
|
|
|
},
|
|
},
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+ const indexMutation = useMutation({
|
|
|
|
|
+ mutationFn: ({ dataset, file }: { dataset: string; file: string }) =>
|
|
|
|
|
+ post(`/files/${dataset}/${encodeURIComponent(file)}/index`),
|
|
|
|
|
+ onSuccess: (result, { file }) => {
|
|
|
|
|
+ queryClient.refetchQueries({ queryKey: ["all-files"] });
|
|
|
|
|
+ toast.success("File indexed successfully");
|
|
|
|
|
+ addNotification({
|
|
|
|
|
+ type: "success",
|
|
|
|
|
+ title: "File Indexed",
|
|
|
|
|
+ message: `File "${file.split("/").pop()}" has been indexed.`,
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ onError: (error, { file }) => {
|
|
|
|
|
+ console.error("Failed to index file:", error);
|
|
|
|
|
+ toast.error("Failed to index file");
|
|
|
|
|
+ addNotification({
|
|
|
|
|
+ type: "error",
|
|
|
|
|
+ title: "Index Failed",
|
|
|
|
|
+ message: `Failed to index file "${file.split("/").pop()}". Please try again.`,
|
|
|
|
|
+ });
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const copyToClipboard = (text: string, label: string) => {
|
|
|
|
|
+ navigator.clipboard.writeText(text);
|
|
|
|
|
+ toast.success(`${label} copied to clipboard`);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
const handleEdit = (file: any) => {
|
|
const handleEdit = (file: any) => {
|
|
|
setEditFile(file);
|
|
setEditFile(file);
|
|
|
};
|
|
};
|
|
@@ -352,6 +382,12 @@ export default function FileList() {
|
|
|
case "delete":
|
|
case "delete":
|
|
|
handleDeleteClick(file);
|
|
handleDeleteClick(file);
|
|
|
break;
|
|
break;
|
|
|
|
|
+ case "index":
|
|
|
|
|
+ indexMutation.mutate({
|
|
|
|
|
+ dataset: file.dataset,
|
|
|
|
|
+ file: file.input,
|
|
|
|
|
+ });
|
|
|
|
|
+ break;
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -601,7 +637,7 @@ export default function FileList() {
|
|
|
className="px-4 py-3 text-left text-xs font-semibold text-gray-700 dark:text-gray-200 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
className="px-4 py-3 text-left text-xs font-semibold text-gray-700 dark:text-gray-200 uppercase tracking-wider cursor-pointer hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
|
onClick={() => handleSort("input")}
|
|
onClick={() => handleSort("input")}
|
|
|
>
|
|
>
|
|
|
- Input{" "}
|
|
|
|
|
|
|
+ File{" "}
|
|
|
{sortField === "input" &&
|
|
{sortField === "input" &&
|
|
|
(sortDirection === "asc" ? "↑" : "↓")}
|
|
(sortDirection === "asc" ? "↑" : "↓")}
|
|
|
</th>
|
|
</th>
|
|
@@ -659,9 +695,9 @@ export default function FileList() {
|
|
|
{file.dataset}
|
|
{file.dataset}
|
|
|
</span>
|
|
</span>
|
|
|
</td>
|
|
</td>
|
|
|
- <td className="px-4 py-2 text-sm text-gray-900 dark:text-gray-100 max-w-xs">
|
|
|
|
|
|
|
+ <td className="px-4 py-2 text-sm text-gray-900 dark:text-gray-100">
|
|
|
<div className="truncate" title={file.input}>
|
|
<div className="truncate" title={file.input}>
|
|
|
- {file.input}
|
|
|
|
|
|
|
+ {file.input.split("/").pop()}
|
|
|
</div>
|
|
</div>
|
|
|
</td>
|
|
</td>
|
|
|
<td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
<td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
|
|
@@ -688,8 +724,17 @@ export default function FileList() {
|
|
|
<ChevronDownIcon className="h-4 w-4" />
|
|
<ChevronDownIcon className="h-4 w-4" />
|
|
|
</button>
|
|
</button>
|
|
|
{openDropdown === fileId && (
|
|
{openDropdown === fileId && (
|
|
|
- <div className="dropdown-container absolute right-0 mt-1 w-32 bg-white dark:bg-gray-800 rounded-md shadow-lg border border-gray-200 dark:border-gray-700 z-10">
|
|
|
|
|
|
|
+ <div className="dropdown-container absolute right-0 mt-1 w-40 bg-white dark:bg-gray-800 rounded-md shadow-lg border border-gray-200 dark:border-gray-700 z-10">
|
|
|
<div className="py-1">
|
|
<div className="py-1">
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={() =>
|
|
|
|
|
+ handleDropdownAction("index", file)
|
|
|
|
|
+ }
|
|
|
|
|
+ className="block w-full text-left px-3 py-2 text-xs text-blue-600 dark:text-blue-400 hover:bg-gray-100 dark:hover:bg-gray-700"
|
|
|
|
|
+ >
|
|
|
|
|
+ <SparklesIcon className="h-4 w-4 inline mr-2" />
|
|
|
|
|
+ Index File
|
|
|
|
|
+ </button>
|
|
|
<button
|
|
<button
|
|
|
onClick={() =>
|
|
onClick={() =>
|
|
|
handleDropdownAction("edit", file)
|
|
handleDropdownAction("edit", file)
|
|
@@ -718,14 +763,86 @@ export default function FileList() {
|
|
|
key={`${fileId}-expanded`}
|
|
key={`${fileId}-expanded`}
|
|
|
className="bg-gray-50 dark:bg-gray-800"
|
|
className="bg-gray-50 dark:bg-gray-800"
|
|
|
>
|
|
>
|
|
|
- <td colSpan={6} className="px-4 py-3">
|
|
|
|
|
- <div className="text-sm">
|
|
|
|
|
- <div className="font-medium text-gray-900 dark:text-gray-100 mb-2">
|
|
|
|
|
- Output
|
|
|
|
|
|
|
+ <td colSpan={6} className="px-4 py-4">
|
|
|
|
|
+ <div className="space-y-4 text-sm">
|
|
|
|
|
+ {/* Input */}
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div className="font-medium text-gray-900 dark:text-gray-100 mb-1">
|
|
|
|
|
+ Input
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
|
|
+ <div className="text-gray-700 dark:text-gray-300 font-mono text-xs bg-white dark:bg-gray-900 p-2 rounded border flex-1 truncate">
|
|
|
|
|
+ {file.input}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={() =>
|
|
|
|
|
+ copyToClipboard(file.input, "Input")
|
|
|
|
|
+ }
|
|
|
|
|
+ className="inline-flex items-center px-2 py-1 text-xs text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded border border-gray-300 dark:border-gray-600 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
|
|
|
|
|
+ title="Copy input path"
|
|
|
|
|
+ >
|
|
|
|
|
+ <DocumentDuplicateIcon className="h-4 w-4" />
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- <div className="text-gray-700 dark:text-gray-300 font-mono text-xs bg-white dark:bg-gray-900 p-2 rounded border">
|
|
|
|
|
- {file.output || "No output available"}
|
|
|
|
|
|
|
+
|
|
|
|
|
+ {/* Output */}
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div className="font-medium text-gray-900 dark:text-gray-100 mb-1">
|
|
|
|
|
+ Output
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
|
|
+ <div className="text-gray-700 dark:text-gray-300 font-mono text-xs bg-white dark:bg-gray-900 p-2 rounded border flex-1 truncate">
|
|
|
|
|
+ {file.output || "No output"}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ {file.output && (
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={() =>
|
|
|
|
|
+ copyToClipboard(file.output, "Output")
|
|
|
|
|
+ }
|
|
|
|
|
+ className="inline-flex items-center px-2 py-1 text-xs text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded border border-gray-300 dark:border-gray-600 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
|
|
|
|
|
+ title="Copy output path"
|
|
|
|
|
+ >
|
|
|
|
|
+ <DocumentDuplicateIcon className="h-4 w-4" />
|
|
|
|
|
+ </button>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* Hash */}
|
|
|
|
|
+ {file.hash && (
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div className="font-medium text-gray-900 dark:text-gray-100 mb-1">
|
|
|
|
|
+ Hash
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
|
|
+ <div className="text-gray-700 dark:text-gray-300 font-mono text-xs bg-white dark:bg-gray-900 p-2 rounded border flex-1 truncate">
|
|
|
|
|
+ {file.hash}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <button
|
|
|
|
|
+ onClick={() =>
|
|
|
|
|
+ copyToClipboard(file.hash, "Hash")
|
|
|
|
|
+ }
|
|
|
|
|
+ className="inline-flex items-center px-2 py-1 text-xs text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 rounded border border-gray-300 dark:border-gray-600 hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
|
|
|
|
|
+ title="Copy hash"
|
|
|
|
|
+ >
|
|
|
|
|
+ <DocumentDuplicateIcon className="h-4 w-4" />
|
|
|
|
|
+ </button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ {/* File Size */}
|
|
|
|
|
+ {file.file_size && (
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div className="font-medium text-gray-900 dark:text-gray-100 mb-1">
|
|
|
|
|
+ File Size
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="text-gray-700 dark:text-gray-300 font-mono text-xs bg-white dark:bg-gray-900 p-2 rounded border">
|
|
|
|
|
+ {(file.file_size / 1024 / 1024).toFixed(2)} MB ({file.file_size.toLocaleString()} bytes)
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
</div>
|
|
</div>
|
|
|
</td>
|
|
</td>
|
|
|
</tr>
|
|
</tr>
|