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 { 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);
|
||||||
@@ -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} />
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user