Add FileUploadsCard in bottom right corner

This commit is contained in:
David Corbitt
2023-09-07 09:03:32 -07:00
parent c9a53c2b94
commit ed25dcce39
6 changed files with 174 additions and 106 deletions

View File

@@ -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;

View 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>
);
};

View File

@@ -26,26 +26,26 @@ import { validateTrainingRows, type TrainingRow, parseJSONL } from "./validateTr
import { uploadDatasetEntryFile } from "~/utils/azure/website"; import { uploadDatasetEntryFile } from "~/utils/azure/website";
import { formatFileSize } from "~/utils/utils"; import { formatFileSize } from "~/utils/utils";
const ImportDataButton = () => { const UploadDataButton = () => {
const disclosure = useDisclosure(); const disclosure = useDisclosure();
return ( return (
<> <>
<ActionButton <ActionButton
onClick={disclosure.onOpen} onClick={disclosure.onOpen}
label="Import Data" label="Upload Data"
icon={AiOutlineCloudUpload} icon={AiOutlineCloudUpload}
iconBoxSize={4} iconBoxSize={4}
requireBeta 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 dataset = useDataset().data;
const [validationError, setValidationError] = useState<string | null>(null); const [validationError, setValidationError] = useState<string | null>(null);

View File

@@ -25,10 +25,10 @@ import DatasetEntryPaginator from "~/components/datasets/DatasetEntryPaginator";
import { useAppStore } from "~/state/store"; import { useAppStore } from "~/state/store";
import FineTuneButton from "~/components/datasets/FineTuneButton"; import FineTuneButton from "~/components/datasets/FineTuneButton";
import ExperimentButton from "~/components/datasets/ExperimentButton"; 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 DownloadButton from "~/components/datasets/DownloadButton";
import DeleteButton from "~/components/datasets/DeleteButton"; import DeleteButton from "~/components/datasets/DeleteButton";
import FileUploadCard from "~/components/datasets/FileUploadCard"; import FileUploadsCard from "~/components/datasets/FileUploadsCard";
export default function Dataset() { export default function Dataset() {
const utils = api.useContext(); const utils = api.useContext();
@@ -41,19 +41,6 @@ export default function Dataset() {
setName(dataset.data?.name || ""); setName(dataset.data?.name || "");
}, [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(() => { useEffect(() => {
useAppStore.getState().sharedArgumentsEditor.loadMonaco().catch(console.error); useAppStore.getState().sharedArgumentsEditor.loadMonaco().catch(console.error);
}, []); }, []);
@@ -115,16 +102,9 @@ export default function Dataset() {
<DatasetHeaderButtons openDrawer={drawerDisclosure.onOpen} /> <DatasetHeaderButtons openDrawer={drawerDisclosure.onOpen} />
</PageHeaderContainer> </PageHeaderContainer>
<VStack px={8} py={8} alignItems="flex-start" spacing={4} w="full"> <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"> <HStack w="full" justifyContent="flex-end">
<FineTuneButton /> <FineTuneButton />
<ImportDataButton /> <UploadDataButton />
<ExperimentButton /> <ExperimentButton />
{/* <DownloadButton /> */} {/* <DownloadButton /> */}
<DeleteButton /> <DeleteButton />
@@ -133,6 +113,7 @@ export default function Dataset() {
<DatasetEntryPaginator /> <DatasetEntryPaginator />
</VStack> </VStack>
</VStack> </VStack>
<FileUploadsCard />
</AppShell> </AppShell>
<DatasetConfigurationDrawer disclosure={drawerDisclosure} /> <DatasetConfigurationDrawer disclosure={drawerDisclosure} />
</> </>

View File

@@ -3,7 +3,7 @@ import { z } from "zod";
import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc"; import { createTRPCRouter, protectedProcedure } from "~/server/api/trpc";
import { prisma } from "~/server/db"; import { prisma } from "~/server/db";
import { requireCanModifyProject, requireCanViewProject } from "~/utils/accessControl"; 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 { generateServiceClientUrl } from "~/utils/azure/server";
import { queueImportDatasetEntries } from "~/server/tasks/importDatasetEntries.task"; import { queueImportDatasetEntries } from "~/server/tasks/importDatasetEntries.task";
@@ -148,19 +148,33 @@ export const datasetsRouter = createTRPCRouter({
orderBy: { createdAt: "desc" }, orderBy: { createdAt: "desc" },
}); });
}), }),
hideFileUpload: protectedProcedure hideFileUploads: protectedProcedure
.input(z.object({ fileUploadId: z.string() })) .input(z.object({ fileUploadIds: z.string().array() }))
.mutation(async ({ input, ctx }) => { .mutation(async ({ input, ctx }) => {
const { datasetId } = await prisma.datasetFileUpload.findUniqueOrThrow({ if (!input.fileUploadIds.length) return error("No file upload ids provided");
where: { id: input.fileUploadId },
}); const {
const { projectId } = await prisma.dataset.findUniqueOrThrow({ dataset: { projectId, id: datasetId },
where: { id: datasetId }, } = await prisma.datasetFileUpload.findUniqueOrThrow({
where: { id: input.fileUploadIds[0] },
select: {
dataset: {
select: {
id: true,
projectId: true,
},
},
},
}); });
await requireCanModifyProject(projectId, ctx); await requireCanModifyProject(projectId, ctx);
await prisma.datasetFileUpload.update({ await prisma.datasetFileUpload.updateMany({
where: { id: input.fileUploadId }, where: {
id: {
in: input.fileUploadIds,
},
datasetId,
},
data: { data: {
visible: false, visible: false,
}, },

View File

@@ -104,6 +104,7 @@ export const importDatasetEntries = defineTask<ImportDatasetEntriesJob>(
data: { data: {
errorMessage: `Error formatting rows: ${e.message as string}`, errorMessage: `Error formatting rows: ${e.message as string}`,
status: "ERROR", status: "ERROR",
visible: true,
}, },
}); });
return; return;
@@ -128,6 +129,7 @@ export const importDatasetEntries = defineTask<ImportDatasetEntriesJob>(
data: { data: {
status: "COMPLETE", status: "COMPLETE",
progress: 100, progress: 100,
visible: true,
}, },
}); });
}, },