-
Notifications
You must be signed in to change notification settings - Fork 811
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
🔄 synced local 'skyvern-frontend/src/' with remote 'skyvern-frontend/…
…src/' <!-- ELLIPSIS_HIDDEN --> | 🚀 | This description was created by [Ellipsis](https://www.ellipsis.dev) for commit d6fb3b10d334b87412cd692095b339b40e72f982 | |--------|--------| ### Summary: Introduced a new 'Workflows' tab with routing, components, API integrations, pagination, loading states, and added a `FileUpload` component with error handling using a toast notification. **Key points**: - Introduced a new 'Workflows' tab with routing, components, API integrations, pagination, and loading states. - Added `WorkflowsPageLayout`, `Workflows`, and `WorkflowPage` components in `skyvern-frontend/src/routes/workflows/`. - Updated `skyvern-frontend/cloud/router.tsx` and `skyvern-frontend/src/router.tsx` to include new routes for workflows. - Modified `skyvern-frontend/cloud/routes/root/SideNav.tsx` to add a 'Workflows (Beta)' link. - Introduced `FileUpload` component in `skyvern-frontend/src/components/FileUpload.tsx` for handling file inputs with error handling using a toast notification. - Added `Checkbox` component in `skyvern-frontend/src/components/ui/checkbox.tsx`. - Updated `skyvern-frontend/package.json` to include `@radix-ui/react-checkbox` dependency. - Adjusted CSS variables in `skyvern-frontend/cloud/index.css` for better theming. - Enabled Sentry only in production in `skyvern-frontend/cloud/index.tsx`. - Added types for workflow parameters and responses in `skyvern-frontend/src/api/types.ts`. - Implemented pagination and loading states in `skyvern-frontend/src/routes/workflows/WorkflowPage.tsx` and `skyvern-frontend/src/routes/workflows/Workflows.tsx`. - Updated `RunWorkflowForm` in `skyvern-frontend/src/routes/workflows/RunWorkflowForm.tsx` to invalidate `workflowRuns` query on success. - Updated `skyvern-frontend/src/routes/workflows/WorkflowRun.tsx` to handle cases where a workflow run does not have any tasks. ---- Generated with ❤️ by [ellipsis.dev](https://www.ellipsis.dev) <!-- ELLIPSIS_HIDDEN -->
- Loading branch information
Showing
17 changed files
with
1,207 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
import { getClient } from "@/api/AxiosClient"; | ||
import { useCredentialGetter } from "@/hooks/useCredentialGetter"; | ||
import { cn } from "@/util/utils"; | ||
import { ReloadIcon } from "@radix-ui/react-icons"; | ||
import { useMutation } from "@tanstack/react-query"; | ||
import { useId, useState } from "react"; | ||
import { Button } from "./ui/button"; | ||
import { Input } from "./ui/input"; | ||
import { Label } from "./ui/label"; | ||
import { toast } from "./ui/use-toast"; | ||
|
||
export type FileInputValue = | ||
| { | ||
s3uri: string; | ||
presignedUrl: string; | ||
} | ||
| string | ||
| null; | ||
|
||
type Props = { | ||
value: FileInputValue; | ||
onChange: (value: FileInputValue) => void; | ||
}; | ||
|
||
const FILE_SIZE_LIMIT_IN_BYTES = 10 * 1024 * 1024; // 10 MB | ||
|
||
function showFileSizeError() { | ||
toast({ | ||
variant: "destructive", | ||
title: "File size limit exceeded", | ||
description: | ||
"The file you are trying to upload exceeds the 10MB limit, please try again with a different file", | ||
}); | ||
} | ||
|
||
function FileUpload({ value, onChange }: Props) { | ||
const credentialGetter = useCredentialGetter(); | ||
const [file, setFile] = useState<File | null>(null); | ||
const [fileUrl, setFileUrl] = useState<string>(""); | ||
const [highlight, setHighlight] = useState(false); | ||
const inputId = useId(); | ||
|
||
const uploadFileMutation = useMutation({ | ||
mutationFn: async (file: File) => { | ||
const client = await getClient(credentialGetter); | ||
const formData = new FormData(); | ||
formData.append("file", file); | ||
return client.post< | ||
FormData, | ||
{ | ||
data: { | ||
s3_uri: string; | ||
presigned_url: string; | ||
}; | ||
} | ||
>("/upload_file", formData, { | ||
headers: { | ||
"Content-Type": "multipart/form-data", | ||
}, | ||
}); | ||
}, | ||
onSuccess: (response) => { | ||
onChange({ | ||
s3uri: response.data.s3_uri, | ||
presignedUrl: response.data.presigned_url, | ||
}); | ||
}, | ||
onError: (error) => { | ||
setFile(null); | ||
toast({ | ||
variant: "destructive", | ||
title: "Failed to upload file", | ||
description: `An error occurred while uploading the file: ${error.message}`, | ||
}); | ||
}, | ||
}); | ||
|
||
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => { | ||
if (e.target.files && e.target.files.length > 0) { | ||
const file = e.target.files[0] as File; | ||
if (file.size > FILE_SIZE_LIMIT_IN_BYTES) { | ||
showFileSizeError(); | ||
return; | ||
} | ||
setFile(file); | ||
uploadFileMutation.mutate(file); | ||
} | ||
}; | ||
|
||
function reset() { | ||
setFile(null); | ||
onChange(null); | ||
} | ||
|
||
if (value === null) { | ||
return ( | ||
<div className="flex gap-4"> | ||
<div className="w-1/2"> | ||
<Label | ||
htmlFor={inputId} | ||
className={cn( | ||
"flex w-full cursor-pointer border border-dashed items-center justify-center py-8", | ||
{ | ||
"border-slate-500": highlight, | ||
}, | ||
)} | ||
onDragEnter={(event) => { | ||
event.preventDefault(); | ||
event.stopPropagation(); | ||
setHighlight(true); | ||
}} | ||
onDragOver={(event) => { | ||
event.preventDefault(); | ||
event.stopPropagation(); | ||
setHighlight(true); | ||
}} | ||
onDragLeave={(event) => { | ||
event.preventDefault(); | ||
event.stopPropagation(); | ||
setHighlight(false); | ||
}} | ||
onDrop={(event) => { | ||
event.preventDefault(); | ||
event.stopPropagation(); | ||
setHighlight(false); | ||
if ( | ||
event.dataTransfer.files && | ||
event.dataTransfer.files.length > 0 | ||
) { | ||
const file = event.dataTransfer.files[0] as File; | ||
if (file.size > FILE_SIZE_LIMIT_IN_BYTES) { | ||
showFileSizeError(); | ||
return; | ||
} | ||
setFile(file); | ||
uploadFileMutation.mutate(file); | ||
} | ||
}} | ||
> | ||
<input | ||
id={inputId} | ||
type="file" | ||
onChange={handleFileChange} | ||
accept=".csv" | ||
className="hidden" | ||
/> | ||
<div className="max-w-full truncate flex gap-2"> | ||
{uploadFileMutation.isPending && ( | ||
<ReloadIcon className="mr-2 h-4 w-4 animate-spin" /> | ||
)} | ||
<span> | ||
{file | ||
? file.name | ||
: "Drag and drop file here or click to select (Max 10MB)"} | ||
</span> | ||
</div> | ||
</Label> | ||
</div> | ||
<div className="flex flex-col items-center justify-center before:flex before:content-[''] before:bg-slate-600"> | ||
OR | ||
</div> | ||
<div className="w-1/2"> | ||
<Label>File URL</Label> | ||
<div className="flex gap-2"> | ||
<Input | ||
value={fileUrl} | ||
onChange={(e) => setFileUrl(e.target.value)} | ||
/> | ||
<Button | ||
onClick={() => { | ||
onChange(fileUrl); | ||
}} | ||
> | ||
Save | ||
</Button> | ||
</div> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
||
if (typeof value === "string") { | ||
return ( | ||
<div className="flex gap-4 items-center"> | ||
<span>{value}</span> | ||
<Button onClick={() => reset()}>Change</Button> | ||
</div> | ||
); | ||
} | ||
|
||
if (typeof value === "object" && file && "s3uri" in value) { | ||
return ( | ||
<div className="flex gap-4 items-center"> | ||
<a href={value.presignedUrl} className="underline"> | ||
<span>{file.name}</span> | ||
</a> | ||
<Button onClick={() => reset()}>Change</Button> | ||
</div> | ||
); | ||
} | ||
} | ||
|
||
export { FileUpload }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import * as React from "react"; | ||
import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; | ||
import { CheckIcon } from "@radix-ui/react-icons"; | ||
|
||
import { cn } from "@/util/utils"; | ||
|
||
const Checkbox = React.forwardRef< | ||
React.ElementRef<typeof CheckboxPrimitive.Root>, | ||
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> | ||
>(({ className, ...props }, ref) => ( | ||
<CheckboxPrimitive.Root | ||
ref={ref} | ||
className={cn( | ||
"peer h-4 w-4 shrink-0 rounded-sm border border-primary shadow focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground", | ||
className, | ||
)} | ||
{...props} | ||
> | ||
<CheckboxPrimitive.Indicator | ||
className={cn("flex items-center justify-center text-current")} | ||
> | ||
<CheckIcon className="h-4 w-4" /> | ||
</CheckboxPrimitive.Indicator> | ||
</CheckboxPrimitive.Root> | ||
)); | ||
Checkbox.displayName = CheckboxPrimitive.Root.displayName; | ||
|
||
export { Checkbox }; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.