Kaynağa Gözat

Fix file list expansion and auto-fill indexing destination

Timothy Pomeroy 4 hafta önce
ebeveyn
işleme
f308362a46
2 değiştirilmiş dosya ile 143 ekleme ve 118 silme
  1. 110 116
      apps/web/src/app/files/FileList.tsx
  2. 33 2
      apps/web/src/app/indexing/page.tsx

+ 110 - 116
apps/web/src/app/files/FileList.tsx

@@ -3,10 +3,10 @@
 import {
   ChevronDownIcon,
   ChevronRightIcon,
-  TrashIcon
+  TrashIcon,
 } from "@heroicons/react/24/outline";
 import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
-import { useEffect, useMemo, useState } from "react";
+import { Fragment, useEffect, useMemo, useState } from "react";
 import toast from "react-hot-toast";
 import { del, get, post } from "../../lib/api";
 import ConfirmationDialog from "../components/ConfirmationDialog";
@@ -25,7 +25,7 @@ export default function FileList() {
   const {
     data: allFiles,
     isLoading,
-    error
+    error,
   } = useQuery({
     queryKey: ["all-files", datasets],
     queryFn: async () => {
@@ -35,11 +35,11 @@ export default function FileList() {
       );
       const results = await Promise.all(allFilesPromises);
       return results.flat().map((file: any) => ({
-        ...file
+        ...file,
       }));
     },
     enabled: !!datasets && datasets.length > 0,
-    staleTime: 30000 // 30 seconds
+    staleTime: 30000, // 30 seconds
   });
 
   // State for filters and search - initialize from localStorage
@@ -166,7 +166,7 @@ export default function FileList() {
     isBatch?: boolean;
     selectedFiles?: any[];
   }>({
-    isOpen: false
+    isOpen: false,
   });
 
   // Batch selection state
@@ -204,14 +204,14 @@ export default function FileList() {
         addNotification({
           type: "success",
           title: "Files Deleted",
-          message: `${params.files.length} files have been deleted successfully.`
+          message: `${params.files.length} files have been deleted successfully.`,
         });
       } else {
         toast.success("File deleted successfully");
         addNotification({
           type: "success",
           title: "File Deleted",
-          message: `File "${params.file}" has been deleted successfully.`
+          message: `File "${params.file}" has been deleted successfully.`,
         });
       }
     },
@@ -221,9 +221,9 @@ export default function FileList() {
       addNotification({
         type: "error",
         title: "Delete Failed",
-        message: `Failed to delete file(s). Please try again.`
+        message: `Failed to delete file(s). Please try again.`,
       });
-    }
+    },
   });
 
   const requeueMutation = useMutation({
@@ -236,7 +236,7 @@ export default function FileList() {
       addNotification({
         type: "success",
         title: "File Requeued",
-        message: `File "${file}" has been requeued for processing.`
+        message: `File "${file}" has been requeued for processing.`,
       });
     },
     onError: (error, { file }) => {
@@ -245,9 +245,9 @@ export default function FileList() {
       addNotification({
         type: "error",
         title: "Requeue Failed",
-        message: `Failed to requeue file "${file}". Please try again.`
+        message: `Failed to requeue file "${file}". Please try again.`,
       });
-    }
+    },
   });
 
   const handleEdit = (file: any) => {
@@ -263,7 +263,7 @@ export default function FileList() {
     setDeleteConfirm({
       isOpen: true,
       file,
-      isBatch: false
+      isBatch: false,
     });
   };
 
@@ -274,7 +274,7 @@ export default function FileList() {
     setDeleteConfirm({
       isOpen: true,
       isBatch: true,
-      selectedFiles: filesToDelete
+      selectedFiles: filesToDelete,
     });
   };
 
@@ -285,7 +285,7 @@ export default function FileList() {
     filesToRequeue.forEach((file) => {
       requeueMutation.mutate({
         dataset: file.dataset,
-        file: file.input
+        file: file.input,
       });
     });
     setSelectedFiles(new Set()); // Clear selection after requeue
@@ -629,121 +629,115 @@ export default function FileList() {
                     const fileId = `${file.dataset}-${file.input}`;
                     const isExpanded = expandedRows.has(fileId);
                     return (
-                      <tr
-                        key={fileId}
-                        className="hover:bg-gray-50 dark:hover:bg-gray-700"
-                      >
-                        <td className="px-4 py-2 whitespace-nowrap">
-                          <button
-                            onClick={() => handleToggleExpanded(fileId)}
-                            className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
-                          >
-                            {isExpanded ? (
-                              <ChevronDownIcon className="h-4 w-4" />
-                            ) : (
-                              <ChevronRightIcon className="h-4 w-4" />
-                            )}
-                          </button>
-                        </td>
-                        <td className="px-4 py-2 whitespace-nowrap">
-                          <input
-                            type="checkbox"
-                            checked={selectedFiles.has(fileId)}
-                            onChange={() => handleSelectFile(file)}
-                            className="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500"
-                          />
-                        </td>
-                        <td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
-                          <span className="px-2 py-1 bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded text-xs font-medium">
-                            {file.dataset}
-                          </span>
-                        </td>
-                        <td className="px-4 py-2 text-sm text-gray-900 dark:text-gray-100 max-w-xs">
-                          <div className="truncate" title={file.input}>
-                            {file.input}
-                          </div>
-                        </td>
-                        <td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
-                          {formatDate(file.date)}
-                        </td>
-                        <td className="px-4 py-2 whitespace-nowrap text-sm text-right">
-                          <div className="flex items-center justify-end space-x-1">
+                      <Fragment key={fileId}>
+                        <tr
+                          key={fileId}
+                          className="hover:bg-gray-50 dark:hover:bg-gray-700"
+                        >
+                          <td className="px-4 py-2 whitespace-nowrap">
                             <button
-                              onClick={() =>
-                                requeueMutation.mutate({
-                                  dataset: file.dataset,
-                                  file: file.input
-                                })
-                              }
-                              className="inline-flex items-center px-3 py-1 text-xs font-medium text-white bg-blue-500 hover:bg-blue-600 rounded-l-md border border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
+                              onClick={() => handleToggleExpanded(fileId)}
+                              className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300"
                             >
-                              Requeue
+                              {isExpanded ? (
+                                <ChevronDownIcon className="h-4 w-4" />
+                              ) : (
+                                <ChevronRightIcon className="h-4 w-4" />
+                              )}
                             </button>
-                            <div className="relative">
+                          </td>
+                          <td className="px-4 py-2 whitespace-nowrap">
+                            <input
+                              type="checkbox"
+                              checked={selectedFiles.has(fileId)}
+                              onChange={() => handleSelectFile(file)}
+                              className="rounded border-gray-300 dark:border-gray-600 text-blue-600 focus:ring-blue-500"
+                            />
+                          </td>
+                          <td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
+                            <span className="px-2 py-1 bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 rounded text-xs font-medium">
+                              {file.dataset}
+                            </span>
+                          </td>
+                          <td className="px-4 py-2 text-sm text-gray-900 dark:text-gray-100 max-w-xs">
+                            <div className="truncate" title={file.input}>
+                              {file.input}
+                            </div>
+                          </td>
+                          <td className="px-4 py-2 whitespace-nowrap text-sm text-gray-900 dark:text-gray-100">
+                            {formatDate(file.date)}
+                          </td>
+                          <td className="px-4 py-2 whitespace-nowrap text-sm text-right">
+                            <div className="flex items-center justify-end space-x-1">
                               <button
-                                onClick={() => handleToggleDropdown(fileId)}
-                                className="inline-flex items-center px-2 py-1 text-xs font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-l-0 border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-r-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
+                                onClick={() =>
+                                  requeueMutation.mutate({
+                                    dataset: file.dataset,
+                                    file: file.input,
+                                  })
+                                }
+                                className="inline-flex items-center px-3 py-1 text-xs font-medium text-white bg-blue-500 hover:bg-blue-600 rounded-l-md border border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
                               >
-                                <ChevronDownIcon className="h-4 w-4" />
+                                Requeue
                               </button>
-                              {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="py-1">
-                                    <button
-                                      onClick={() =>
-                                        handleDropdownAction("edit", file)
-                                      }
-                                      className="block w-full text-left px-3 py-2 text-xs text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
-                                    >
-                                      Edit
-                                    </button>
-                                    <button
-                                      onClick={() =>
-                                        handleDropdownAction("delete", file)
-                                      }
-                                      className="block w-full text-left px-3 py-2 text-xs text-red-600 dark:text-red-400 hover:bg-gray-100 dark:hover:bg-gray-700"
-                                    >
-                                      Delete
-                                    </button>
+                              <div className="relative">
+                                <button
+                                  onClick={() => handleToggleDropdown(fileId)}
+                                  className="inline-flex items-center px-2 py-1 text-xs font-medium text-gray-700 dark:text-gray-300 bg-white dark:bg-gray-800 border border-l-0 border-gray-300 dark:border-gray-600 hover:bg-gray-50 dark:hover:bg-gray-700 rounded-r-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
+                                >
+                                  <ChevronDownIcon className="h-4 w-4" />
+                                </button>
+                                {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="py-1">
+                                      <button
+                                        onClick={() =>
+                                          handleDropdownAction("edit", file)
+                                        }
+                                        className="block w-full text-left px-3 py-2 text-xs text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700"
+                                      >
+                                        Edit
+                                      </button>
+                                      <button
+                                        onClick={() =>
+                                          handleDropdownAction("delete", file)
+                                        }
+                                        className="block w-full text-left px-3 py-2 text-xs text-red-600 dark:text-red-400 hover:bg-gray-100 dark:hover:bg-gray-700"
+                                      >
+                                        Delete
+                                      </button>
+                                    </div>
                                   </div>
-                                </div>
-                              )}
-                            </div>
-                          </div>
-                        </td>
-                      </tr>
-                    );
-                  })}
-                  {/* Render expanded rows separately */}
-                  {displayFiles
-                    .filter((file: any) =>
-                      expandedRows.has(`${file.dataset}-${file.input}`)
-                    )
-                    .map((file: any) => {
-                      const fileId = `${file.dataset}-${file.input}`;
-                      return (
-                        <tr
-                          key={`${fileId}-expanded`}
-                          className="bg-gray-50 dark:bg-gray-800"
-                        >
-                          <td colSpan={5} className="px-4 py-3">
-                            <div className="text-sm">
-                              <div className="font-medium text-gray-900 dark:text-gray-100 mb-2">
-                                Output
-                              </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"}
+                                )}
                               </div>
                             </div>
                           </td>
                         </tr>
-                      );
-                    })}
+                        {isExpanded && (
+                          <tr
+                            key={`${fileId}-expanded`}
+                            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
+                                </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"}
+                                </div>
+                              </div>
+                            </td>
+                          </tr>
+                        )}
+                      </Fragment>
+                    );
+                  })}
                 </>
               ) : (
                 <tr>
                   <td
-                    colSpan={5}
+                    colSpan={6}
                     className="px-4 py-4 text-center text-gray-500 dark:text-gray-400"
                   >
                     No files found matching your filters.

+ 33 - 2
apps/web/src/app/indexing/page.tsx

@@ -7,7 +7,7 @@ import {
   TrashIcon,
 } from "@heroicons/react/24/outline";
 import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
-import { useState } from "react";
+import { useEffect, useState } from "react";
 import toast from "react-hot-toast";
 import { del, get, post } from "../../lib/api";
 import LoadingCard from "../components/Loading";
@@ -30,7 +30,7 @@ interface IndexCount {
 
 export default function IndexManagementPage() {
   const queryClient = useQueryClient();
-  const { datasets } = useAppContext();
+  const { datasets, datasetsConfig } = useAppContext();
   const [selectedDataset, setSelectedDataset] = useState<string>("");
   const [destinationPath, setDestinationPath] = useState<string>("");
   const [batchSize, setBatchSize] = useState<number>(100);
@@ -39,6 +39,37 @@ export default function IndexManagementPage() {
     ? datasets.map((p: string) => p.split("/").pop()).filter(Boolean)
     : [];
 
+  // Auto-populate destination path from dataset configuration when a dataset is selected
+  // We mimic the backend collector: prefer top-level destination, otherwise any nested config with a destination key
+  useEffect(() => {
+    if (!selectedDataset || !datasetsConfig) return;
+
+    const cfg = datasetsConfig[selectedDataset];
+    if (!cfg) return;
+
+    const tryFindDestination = (obj: any): string | undefined => {
+      if (!obj || typeof obj !== "object") return undefined;
+      if (typeof obj.destination === "string" && obj.destination.trim()) {
+        return obj.destination as string;
+      }
+      for (const value of Object.values(obj)) {
+        if (
+          value &&
+          typeof value === "object" &&
+          typeof value.destination === "string"
+        ) {
+          if (value.destination.trim()) return value.destination as string;
+        }
+      }
+      return undefined;
+    };
+
+    const destination = tryFindDestination(cfg);
+    if (destination && destination !== destinationPath) {
+      setDestinationPath(destination);
+    }
+  }, [selectedDataset, datasetsConfig, destinationPath]);
+
   // Get index count for selected dataset
   const {
     data: indexCount,