Skip to content

Commit

Permalink
Improve upload form components to support urls as well as file uploads
Browse files Browse the repository at this point in the history
  • Loading branch information
philmcmahon committed Oct 22, 2024
1 parent 327b902 commit a50fde3
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 80 deletions.
4 changes: 0 additions & 4 deletions packages/client/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,6 @@ import { UploadForm } from '../components/UploadForm';
const Home = () => {
return (
<>
<p className={' pb-3 font-light'}>
Use the form below to upload your files for transcription. You will
receive an email when the transcription process is complete.
</p>
<UploadForm></UploadForm>
</>
);
Expand Down
3 changes: 3 additions & 0 deletions packages/client/src/components/InfoMessage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ import { RequestStatus } from '@/types';
import { Spinner } from 'flowbite-react';
import {
CheckCircleIcon,
ExclamationCircleIcon,
ExclamationTriangleIcon,
} from '@heroicons/react/16/solid';

export const iconForStatus = (status: RequestStatus) => {
switch (status) {
case RequestStatus.InProgress:
return <Spinner className={'w-6 h-6'} />;
case RequestStatus.Invalid:
return <ExclamationCircleIcon className={'w-6 h-6 text-red-500'} />;
case RequestStatus.Failed:
return <ExclamationTriangleIcon className={'w-6 h-6 text-red-500'} />;
case RequestStatus.Success:
Expand Down
39 changes: 39 additions & 0 deletions packages/client/src/components/SubmitResult.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { RequestStatus } from '@/types';
import { UploadProgress } from '@/components/UploadProgress';
import { UploadFailure } from '@/components/UploadFailure';
import { UploadSuccess } from '@/components/UploadSuccess';
import React from 'react';

export const SubmitResult = ({
mediaSource,
formStatus,
mediaWithStatus,
reset,
}: {
mediaSource: 'file' | 'url';
formStatus: RequestStatus;
mediaWithStatus: Record<string, RequestStatus>;
reset: () => void;
}) => {
const uploadsInProgress = Object.entries(mediaWithStatus).length > 0;
const oneOrMoreUploadsFailed =
formStatus === RequestStatus.Failed ||
Object.values(mediaWithStatus).includes(RequestStatus.Failed);
const allUploadsSucceeded =
Object.entries(mediaWithStatus).length > 0 &&
Object.values(mediaWithStatus).filter((s) => s !== RequestStatus.Success)
.length === 0;
return (
<>
{uploadsInProgress && (
<UploadProgress uploads={mediaWithStatus} mediaSource={mediaSource} />
)}
{oneOrMoreUploadsFailed && (
<UploadFailure reset={reset} mediaSource={mediaSource} />
)}
{allUploadsSucceeded && (
<UploadSuccess reset={reset} mediaSource={mediaSource} />
)}
</>
);
};
26 changes: 26 additions & 0 deletions packages/client/src/components/UploadFailure.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import React from 'react';

export const UploadFailure = ({
reset,
mediaSource,
}: {
reset: () => void;
mediaSource: 'file' | 'url';
}) => {
const text = mediaSource === 'file' ? 'uploads failed' : 'media urls invalid';
return (
<div
className="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400"
role="alert"
>
<span className="font-medium">One or more {text}</span>{' '}
<button
onClick={() => reset()}
className="font-medium text-blue-600 underline dark:text-blue-500 hover:no-underline"
>
Click here
</button>{' '}
to try again
</div>
);
};
123 changes: 47 additions & 76 deletions packages/client/src/components/UploadForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ import {
Alert,
} from 'flowbite-react';
import { RequestStatus } from '@/types';
import { iconForStatus, InfoMessage } from '@/components/InfoMessage';
import { InfoMessage } from '@/components/InfoMessage';
import { SubmitResult } from '@/components/SubmitResult';

const submitMediaUrl = async (
url: string,
Expand Down Expand Up @@ -100,12 +101,19 @@ const updateFileStatus = (
fileName: string,
newStatus: RequestStatus,
) => {
const x = {
return {
...uploads,
[`${index}-${fileName}`]: newStatus,
};
console.log('status', uploads, x);
return x;
};

const checkUrlValid = (url: string) => {
try {
new URL(url);
return true;
} catch {
return false;
}
};

export const UploadForm = () => {
Expand All @@ -122,6 +130,7 @@ export const UploadForm = () => {
useState<boolean>(false);
const [mediaSource, setMediaSource] = useState<'file' | 'url'>('file');
const [mediaUrlText, setMediaUrlText] = useState<string>('');
const [mediaUrls, setMediaUrls] = useState<Record<string, RequestStatus>>({});
const { token } = useContext(AuthContext);

const reset = () => {
Expand All @@ -139,77 +148,15 @@ export const UploadForm = () => {
}

if (status !== RequestStatus.Ready) {
console.log(uploads);
const progressList = mediaSource === 'url' ? mediaUrls : uploads;
console.log(progressList);
return (
<>
{Object.entries(uploads).length > 0 && (
<div className={'pb-10'}>
<h2 className="mb-2 text-lg font-semibold text-gray-900 dark:text-white">
Uploading files:
</h2>

<ul className="max-w-md space-y-2 text-gray-500 list-inside dark:text-gray-400">
{Object.entries(uploads).map(([key, value]) => (
<li className="flex items-center">
<span className={'mr-1'}>{iconForStatus(value)}</span>
{key}
</li>
))}
</ul>
</div>
)}
{(status === RequestStatus.Failed ||
Object.values(uploads).includes(RequestStatus.Failed)) && (
<div
className="p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50 dark:bg-gray-800 dark:text-red-400"
role="alert"
>
<span className="font-medium">One or more uploads failed</span>{' '}
<button
onClick={() => reset()}
className="font-medium text-blue-600 underline dark:text-blue-500 hover:no-underline"
>
Click here
</button>{' '}
to try again
</div>
)}
{Object.entries(uploads).length > 0 &&
Object.values(uploads).filter((s) => s !== RequestStatus.Success)
.length === 0 && (
<div
className="p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50 dark:bg-gray-800 dark:text-green-400"
role="alert"
>
<span className="font-medium">Upload complete. </span>{' '}
<p>
Transcription in progress - check your email for the completed
transcript.{' '}
</p>
<div className="font-medium">
<p>
{' '}
The service can take a few minutes to start up, but thereafter
the transcription process is typically shorter than the length
of the media file.{' '}
</p>
<p>
If you have requested a translation, you will receive 2 emails
- one for the transcription in the original language, another
for the english translation. The emails will arrive at
different times
</p>
</div>
<button
onClick={() => reset()}
className="font-medium text-blue-600 underline dark:text-blue-500 hover:no-underline"
>
Click here
</button>{' '}
to transcribe another file
</div>
)}
</>
<SubmitResult
mediaSource={mediaSource}
formStatus={status}
mediaWithStatus={progressList}
reset={reset}
/>
);
}

Expand All @@ -231,13 +178,33 @@ export const UploadForm = () => {
if (urls.length === 0) {
return;
}
for (const url of urls) {
await submitMediaUrl(
const urlsWithStatus = Object.fromEntries(
urls.map((url) => [
url,
checkUrlValid(url) ? RequestStatus.InProgress : RequestStatus.Invalid,
]),
);
const validUrls = urls.filter(checkUrlValid);
setMediaUrls(urlsWithStatus);
for (const url of validUrls) {
const success = await submitMediaUrl(
url,
token,
mediaFileLanguageCode,
translationRequested,
);
if (success) {
setMediaUrls((prev) => ({
...prev,
[url]: RequestStatus.Success,
}));
} else {
setMediaUrls((prev) => ({
...prev,
[url]: RequestStatus.Failed,
}));
setStatus(RequestStatus.Failed);
}
}
setStatus(RequestStatus.Success);
return;
Expand Down Expand Up @@ -283,6 +250,10 @@ export const UploadForm = () => {

return (
<>
<p className={' pb-3 font-light'}>
Use the form below to upload your files for transcription. You will
receive an email when the transcription process is complete.
</p>
<form id="media-upload-form" onSubmit={handleSubmit}>
<div className="flex items-center gap-2">
I want to transcribe a...
Expand Down
30 changes: 30 additions & 0 deletions packages/client/src/components/UploadProgress.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { iconForStatus } from '@/components/InfoMessage';
import React from 'react';
import { RequestStatus } from '@/types';

export const UploadProgress = ({
uploads,
mediaSource,
}: {
uploads: Record<string, RequestStatus>;
mediaSource: 'file' | 'url';
}) => {
const text =
mediaSource === 'file' ? 'Uploading files:' : 'Processing media urls:';
return (
<div className={'pb-10'}>
<h2 className="mb-2 text-lg font-semibold text-gray-900 dark:text-white">
{text}
</h2>

<ul className="max-w-md space-y-2 text-gray-500 list-inside dark:text-gray-400">
{Object.entries(uploads).map(([key, value]) => (
<li className="flex items-center">
<span className={'mr-1'}>{iconForStatus(value)}</span>
{key} {value === RequestStatus.Invalid && ' (invalid url)'}
</li>
))}
</ul>
</div>
);
};
49 changes: 49 additions & 0 deletions packages/client/src/components/UploadSuccess.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import React from 'react';

export const UploadSuccess = ({
reset,
mediaSource,
}: {
reset: () => void;
mediaSource: 'file' | 'url';
}) => {
const mediaDownloadText = mediaSource === 'url' && (
<p>
The media at the url you submitted will first be downloaded, then
transcribed. The download will take 5 minutes for a 1 hour video
</p>
);
return (
<div
className="p-4 mb-4 text-sm text-green-800 rounded-lg bg-green-50 dark:bg-gray-800 dark:text-green-400"
role="alert"
>
<span className="font-medium">Upload complete. </span>{' '}
<p>
Transcription in progress - check your email for the completed
transcript.{' '}
</p>
<div className="font-medium">
{mediaDownloadText}
<p>
{' '}
The transcription service can take a few minutes to start up, but
thereafter the transcription process is typically shorter than the
length of the media file.{' '}
</p>
<p>
If you have requested a translation, you will receive 2 emails - one
for the transcription in the original language, another for the
english translation. The emails will arrive at different times
</p>
</div>
<button
onClick={() => reset()}
className="font-medium text-blue-600 underline dark:text-blue-500 hover:no-underline"
>
Click here
</button>{' '}
to transcribe another file
</div>
);
};
1 change: 1 addition & 0 deletions packages/client/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export type AuthState = {
export enum RequestStatus {
Ready = 'Ready',
InProgress = 'InProgress',
Invalid = 'Invalid',
Success = 'Success',
Failed = 'Failed',
}

0 comments on commit a50fde3

Please sign in to comment.