Add FileUploadsCard in bottom right corner
This commit is contained in:
@@ -1,68 +0,0 @@
|
||||
import { VStack, HStack, Button, Text, Card, Progress, IconButton } from "@chakra-ui/react";
|
||||
import { BsX } from "react-icons/bs";
|
||||
|
||||
import { type RouterOutputs, api } from "~/utils/api";
|
||||
import { useHandledAsyncCallback } from "~/utils/hooks";
|
||||
import { formatFileSize } from "~/utils/utils";
|
||||
|
||||
type FileUpload = RouterOutputs["datasets"]["listFileUploads"][0];
|
||||
|
||||
const FileUploadCard = ({ fileUpload }: { fileUpload: FileUpload }) => {
|
||||
const { id, fileName, fileSize, progress, status, errorMessage } = fileUpload;
|
||||
|
||||
const utils = api.useContext();
|
||||
|
||||
const hideFileUploadMutation = api.datasets.hideFileUpload.useMutation();
|
||||
const [hideFileUpload, hidingInProgress] = useHandledAsyncCallback(async () => {
|
||||
await hideFileUploadMutation.mutateAsync({ fileUploadId: id });
|
||||
await utils.datasets.listFileUploads.invalidate();
|
||||
}, [id, hideFileUploadMutation, utils]);
|
||||
|
||||
const [refreshDatasetEntries] = useHandledAsyncCallback(async () => {
|
||||
await utils.datasetEntries.list.invalidate();
|
||||
}, [utils]);
|
||||
|
||||
return (
|
||||
<Card w="full">
|
||||
<VStack w="full" alignItems="flex-start" p={4}>
|
||||
<HStack w="full" justifyContent="space-between">
|
||||
<Text fontWeight="bold">
|
||||
Uploading {fileName} ({formatFileSize(fileSize, 2)})
|
||||
</Text>
|
||||
<HStack spacing={0}>
|
||||
{status === "COMPLETE" && (
|
||||
<Button variant="ghost" onClick={refreshDatasetEntries} color="orange.400" size="xs">
|
||||
Refresh Table
|
||||
</Button>
|
||||
)}
|
||||
<IconButton
|
||||
aria-label="Hide file upload"
|
||||
as={BsX}
|
||||
boxSize={6}
|
||||
minW={0}
|
||||
variant="ghost"
|
||||
isLoading={hidingInProgress}
|
||||
onClick={hideFileUpload}
|
||||
cursor="pointer"
|
||||
/>
|
||||
</HStack>
|
||||
</HStack>
|
||||
|
||||
{errorMessage ? (
|
||||
<Text alignSelf="center" pt={2}>
|
||||
{errorMessage}
|
||||
</Text>
|
||||
) : (
|
||||
<>
|
||||
<Text alignSelf="center" fontSize="xs">
|
||||
{status} ({progress}%)
|
||||
</Text>
|
||||
<Progress w="full" value={progress} borderRadius={2} />
|
||||
</>
|
||||
)}
|
||||
</VStack>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileUploadCard;
|
||||
139
app/src/components/datasets/FileUploadsCard.tsx
Normal file
139
app/src/components/datasets/FileUploadsCard.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import {
|
||||
VStack,
|
||||
HStack,
|
||||
Button,
|
||||
Text,
|
||||
Progress,
|
||||
IconButton,
|
||||
Portal,
|
||||
Spinner,
|
||||
} from "@chakra-ui/react";
|
||||
import { BsX } from "react-icons/bs";
|
||||
|
||||
import { type RouterOutputs, api } from "~/utils/api";
|
||||
import { useDataset, useHandledAsyncCallback } from "~/utils/hooks";
|
||||
import { formatFileSize } from "~/utils/utils";
|
||||
|
||||
type FileUpload = RouterOutputs["datasets"]["listFileUploads"][0];
|
||||
|
||||
const FileUploadsCard = () => {
|
||||
const dataset = useDataset();
|
||||
const [fileUploadsRefetchInterval, setFileUploadsRefetchInterval] = useState<number>(500);
|
||||
const fileUploads = api.datasets.listFileUploads.useQuery(
|
||||
{ datasetId: dataset.data?.id as string },
|
||||
{ enabled: !!dataset.data?.id, refetchInterval: fileUploadsRefetchInterval },
|
||||
);
|
||||
useEffect(() => {
|
||||
if (fileUploads?.data?.some((fu) => fu.status !== "COMPLETE" && fu.status !== "ERROR")) {
|
||||
setFileUploadsRefetchInterval(500);
|
||||
} else {
|
||||
setFileUploadsRefetchInterval(15000);
|
||||
}
|
||||
}, [fileUploads]);
|
||||
|
||||
const utils = api.useContext();
|
||||
|
||||
const hideFileUploadsMutation = api.datasets.hideFileUploads.useMutation();
|
||||
const [hideAllFileUploads, hidingInProgress] = useHandledAsyncCallback(async () => {
|
||||
if (!fileUploads.data?.length) return;
|
||||
await hideFileUploadsMutation.mutateAsync({
|
||||
fileUploadIds: fileUploads.data.map((upload) => upload.id),
|
||||
});
|
||||
await utils.datasets.listFileUploads.invalidate();
|
||||
}, [hideFileUploadsMutation, fileUploads.data, utils]);
|
||||
|
||||
if (!fileUploads.data?.length) return null;
|
||||
|
||||
return (
|
||||
<Portal>
|
||||
<VStack
|
||||
w={72}
|
||||
borderRadius={8}
|
||||
position="fixed"
|
||||
bottom={8}
|
||||
right={8}
|
||||
overflow="hidden"
|
||||
borderWidth={1}
|
||||
boxShadow="0 0 40px 4px rgba(0, 0, 0, 0.1);"
|
||||
minW={0}
|
||||
bgColor="white"
|
||||
>
|
||||
<HStack p={4} w="full" bgColor="gray.200" justifyContent="space-between">
|
||||
<Text fontWeight="bold">Uploads</Text>
|
||||
<IconButton
|
||||
aria-label="Close uploads"
|
||||
as={hidingInProgress ? Spinner : BsX}
|
||||
boxSize={6}
|
||||
minW={0}
|
||||
variant="ghost"
|
||||
onClick={hideAllFileUploads}
|
||||
cursor="pointer"
|
||||
/>
|
||||
</HStack>
|
||||
{fileUploads?.data?.map((upload) => <FileUploadRow key={upload.id} fileUpload={upload} />)}
|
||||
</VStack>
|
||||
</Portal>
|
||||
);
|
||||
};
|
||||
|
||||
export default FileUploadsCard;
|
||||
|
||||
const FileUploadRow = ({ fileUpload }: { fileUpload: FileUpload }) => {
|
||||
const { id, fileName, fileSize, progress, status, errorMessage } = fileUpload;
|
||||
|
||||
const utils = api.useContext();
|
||||
|
||||
const hideFileUploadsMutation = api.datasets.hideFileUploads.useMutation();
|
||||
const [hideFileUpload, hidingInProgress] = useHandledAsyncCallback(async () => {
|
||||
await hideFileUploadsMutation.mutateAsync({ fileUploadIds: [id] });
|
||||
}, [id, hideFileUploadsMutation, utils]);
|
||||
|
||||
const [refreshDatasetEntries] = useHandledAsyncCallback(async () => {
|
||||
await hideFileUploadsMutation.mutateAsync({ fileUploadIds: [id] });
|
||||
await utils.datasetEntries.list.invalidate();
|
||||
}, [id, hideFileUploadsMutation, utils]);
|
||||
|
||||
return (
|
||||
<VStack w="full" alignItems="flex-start" p={4} borderBottomWidth={1}>
|
||||
<HStack w="full" justifyContent="space-between" alignItems="flex-start">
|
||||
<VStack alignItems="flex-start" spacing={0}>
|
||||
<Text fontWeight="bold">{fileName}</Text>
|
||||
<Text fontSize="xs">({formatFileSize(fileSize, 2)})</Text>
|
||||
</VStack>
|
||||
|
||||
<HStack spacing={0}>
|
||||
{status === "COMPLETE" ? (
|
||||
<Button variant="ghost" onClick={refreshDatasetEntries} color="orange.400" size="xs">
|
||||
Refresh Table
|
||||
</Button>
|
||||
) : (
|
||||
<IconButton
|
||||
aria-label="Hide file upload"
|
||||
as={BsX}
|
||||
boxSize={6}
|
||||
minW={0}
|
||||
variant="ghost"
|
||||
isLoading={hidingInProgress}
|
||||
onClick={hideFileUpload}
|
||||
cursor="pointer"
|
||||
/>
|
||||
)}
|
||||
</HStack>
|
||||
</HStack>
|
||||
|
||||
{errorMessage ? (
|
||||
<Text alignSelf="center" pt={2}>
|
||||
{errorMessage}
|
||||
</Text>
|
||||
) : (
|
||||
<>
|
||||
<Text alignSelf="center" fontSize="xs">
|
||||
{status}
|
||||
</Text>
|
||||
<Progress w="full" value={progress} borderRadius={2} />
|
||||
</>
|
||||
)}
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
@@ -26,26 +26,26 @@ import { validateTrainingRows, type TrainingRow, parseJSONL } from "./validateTr
|
||||
import { uploadDatasetEntryFile } from "~/utils/azure/website";
|
||||
import { formatFileSize } from "~/utils/utils";
|
||||
|
||||
const ImportDataButton = () => {
|
||||
const UploadDataButton = () => {
|
||||
const disclosure = useDisclosure();
|
||||
|
||||
return (
|
||||
<>
|
||||
<ActionButton
|
||||
onClick={disclosure.onOpen}
|
||||
label="Import Data"
|
||||
label="Upload Data"
|
||||
icon={AiOutlineCloudUpload}
|
||||
iconBoxSize={4}
|
||||
requireBeta
|
||||
/>
|
||||
<ImportDataModal disclosure={disclosure} />
|
||||
<UploadDataModal disclosure={disclosure} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImportDataButton;
|
||||
export default UploadDataButton;
|
||||
|
||||
const ImportDataModal = ({ disclosure }: { disclosure: UseDisclosureReturn }) => {
|
||||
const UploadDataModal = ({ disclosure }: { disclosure: UseDisclosureReturn }) => {
|
||||
const dataset = useDataset().data;
|
||||
|
||||
const [validationError, setValidationError] = useState<string | null>(null);
|
||||
@@ -25,10 +25,10 @@ import DatasetEntryPaginator from "~/components/datasets/DatasetEntryPaginator";
|
||||
import { useAppStore } from "~/state/store";
|
||||
import FineTuneButton from "~/components/datasets/FineTuneButton";
|
||||
import ExperimentButton from "~/components/datasets/ExperimentButton";
|
||||
import ImportDataButton from "~/components/datasets/ImportDataButton";
|
||||
import UploadDataButton from "~/components/datasets/UploadDataButton";
|
||||
// import DownloadButton from "~/components/datasets/DownloadButton";
|
||||
import DeleteButton from "~/components/datasets/DeleteButton";
|
||||
import FileUploadCard from "~/components/datasets/FileUploadCard";
|
||||
import FileUploadsCard from "~/components/datasets/FileUploadsCard";
|
||||
|
||||
export default function Dataset() {
|
||||
const utils = api.useContext();
|
||||
@@ -41,19 +41,6 @@ export default function Dataset() {
|
||||
setName(dataset.data?.name || "");
|
||||
}, [dataset.data?.name]);
|
||||
|
||||
const [fileUploadsRefetchInterval, setFileUploadsRefetchInterval] = useState<number>(500);
|
||||
const fileUploads = api.datasets.listFileUploads.useQuery(
|
||||
{ datasetId: dataset.data?.id as string },
|
||||
{ enabled: !!dataset.data?.id, refetchInterval: fileUploadsRefetchInterval },
|
||||
);
|
||||
useEffect(() => {
|
||||
if (fileUploads?.data?.some((fu) => fu.status !== "COMPLETE" && fu.status !== "ERROR")) {
|
||||
setFileUploadsRefetchInterval(500);
|
||||
} else {
|
||||
setFileUploadsRefetchInterval(0);
|
||||
}
|
||||
}, [fileUploads]);
|
||||
|
||||
useEffect(() => {
|
||||
useAppStore.getState().sharedArgumentsEditor.loadMonaco().catch(console.error);
|
||||
}, []);
|
||||
@@ -115,16 +102,9 @@ export default function Dataset() {
|
||||
<DatasetHeaderButtons openDrawer={drawerDisclosure.onOpen} />
|
||||
</PageHeaderContainer>
|
||||
<VStack px={8} py={8} alignItems="flex-start" spacing={4} w="full">
|
||||
<HStack w="full">
|
||||
<VStack w="full">
|
||||
{fileUploads?.data?.map((upload) => (
|
||||
<FileUploadCard key={upload.id} fileUpload={upload} />
|
||||
))}
|
||||
</VStack>
|
||||
</HStack>
|
||||
<HStack w="full" justifyContent="flex-end">
|
||||
<FineTuneButton />
|
||||
<ImportDataButton />
|
||||
<UploadDataButton />
|
||||
<ExperimentButton />
|
||||
{/* <DownloadButton /> */}
|
||||
<DeleteButton />
|
||||
@@ -133,6 +113,7 @@ export default function Dataset() {
|
||||
<DatasetEntryPaginator />
|
||||
</VStack>
|
||||
</VStack>
|
||||
<FileUploadsCard />
|
||||
</AppShell>
|
||||
<DatasetConfigurationDrawer disclosure={drawerDisclosure} />
|
||||
</>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { z } from "zod";
|
||||
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
|
||||
import { prisma } from "~/server/db";
|
||||
import { requireCanModifyProject, requireCanViewProject } from "~/utils/accessControl";
|
||||
import { success } from "~/utils/errorHandling/standardResponses";
|
||||
import { error, success } from "~/utils/errorHandling/standardResponses";
|
||||
import { generateServiceClientUrl } from "~/utils/azure/server";
|
||||
import { queueImportDatasetEntries } from "~/server/tasks/importDatasetEntries.task";
|
||||
|
||||
@@ -148,19 +148,33 @@ export const datasetsRouter = createTRPCRouter({
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
}),
|
||||
hideFileUpload: protectedProcedure
|
||||
.input(z.object({ fileUploadId: z.string() }))
|
||||
hideFileUploads: protectedProcedure
|
||||
.input(z.object({ fileUploadIds: z.string().array() }))
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
const { datasetId } = await prisma.datasetFileUpload.findUniqueOrThrow({
|
||||
where: { id: input.fileUploadId },
|
||||
});
|
||||
const { projectId } = await prisma.dataset.findUniqueOrThrow({
|
||||
where: { id: datasetId },
|
||||
if (!input.fileUploadIds.length) return error("No file upload ids provided");
|
||||
|
||||
const {
|
||||
dataset: { projectId, id: datasetId },
|
||||
} = await prisma.datasetFileUpload.findUniqueOrThrow({
|
||||
where: { id: input.fileUploadIds[0] },
|
||||
select: {
|
||||
dataset: {
|
||||
select: {
|
||||
id: true,
|
||||
projectId: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
await requireCanModifyProject(projectId, ctx);
|
||||
|
||||
await prisma.datasetFileUpload.update({
|
||||
where: { id: input.fileUploadId },
|
||||
await prisma.datasetFileUpload.updateMany({
|
||||
where: {
|
||||
id: {
|
||||
in: input.fileUploadIds,
|
||||
},
|
||||
datasetId,
|
||||
},
|
||||
data: {
|
||||
visible: false,
|
||||
},
|
||||
|
||||
@@ -104,6 +104,7 @@ export const importDatasetEntries = defineTask<ImportDatasetEntriesJob>(
|
||||
data: {
|
||||
errorMessage: `Error formatting rows: ${e.message as string}`,
|
||||
status: "ERROR",
|
||||
visible: true,
|
||||
},
|
||||
});
|
||||
return;
|
||||
@@ -128,6 +129,7 @@ export const importDatasetEntries = defineTask<ImportDatasetEntriesJob>(
|
||||
data: {
|
||||
status: "COMPLETE",
|
||||
progress: 100,
|
||||
visible: true,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user