refactor: final touches
This commit is contained in:
16
README.md
16
README.md
@@ -1,12 +1,16 @@
|
|||||||
|
## PDF to text
|
||||||
|
|
||||||
|
The goal of this project is to create a small web interface that allows the user to upload a PDF file and get the text content of the PDF.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
|
||||||
|
- A small web interface allowing to upload a PDF file
|
||||||
|
- An output containing the text content of the PDF
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
To ruun the development server:
|
To run the development server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
yarn dev
|
yarn dev
|
||||||
```
|
```
|
||||||
|
|
||||||
## TODO
|
|
||||||
|
|
||||||
- edge cases
|
|
||||||
- tests
|
|
||||||
|
|||||||
@@ -1,115 +1,20 @@
|
|||||||
import { responseSchema } from '@/utils/data';
|
import { useState } from 'react';
|
||||||
import axios from 'axios';
|
|
||||||
import { useCallback, useState } from 'react';
|
|
||||||
import { Button } from './Button';
|
|
||||||
import { Content } from './Content';
|
import { Content } from './Content';
|
||||||
|
import { Form } from './Form';
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const [clearing, setClearing] = useState(false);
|
|
||||||
const [file, setFile] = useState<File>();
|
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string>();
|
const [error, setError] = useState<string>();
|
||||||
const [parsedText, setParsedText] = useState<string>('');
|
const [parsedText, setParsedText] = useState<string>('');
|
||||||
|
|
||||||
const handleFileChange = useCallback(
|
|
||||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
if (e.target.files) {
|
|
||||||
setFile(e.target.files[0]);
|
|
||||||
} else {
|
|
||||||
setFile(undefined);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[]
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleClear = useCallback(() => {
|
|
||||||
setClearing(false);
|
|
||||||
setFile(undefined);
|
|
||||||
setParsedText('');
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const validateFile = useCallback((file: File) => {
|
|
||||||
if (file.type !== 'application/pdf') {
|
|
||||||
setError(
|
|
||||||
"The file you've selected is not a PDF file. Please select a PDF file."
|
|
||||||
);
|
|
||||||
setLoading(false);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This limit is caused by the project being deployed on Vercel using a free tier, with limited resources
|
|
||||||
if (file.size > 1024 * 1024) {
|
|
||||||
setError('The file must be smaller than 1MB.');
|
|
||||||
setLoading(false);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const uploadFile = useCallback(async () => {
|
|
||||||
if (!file) return;
|
|
||||||
|
|
||||||
if (!validateFile(file)) return;
|
|
||||||
|
|
||||||
const formData = new FormData();
|
|
||||||
|
|
||||||
formData.append('File', file);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await axios.post('/api/parse', formData);
|
|
||||||
const validatedData = responseSchema.safeParse(response.data);
|
|
||||||
|
|
||||||
if (!validatedData.success) {
|
|
||||||
setError('There was an error parsing the file. Please try again.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setParsedText(validatedData.data.text);
|
|
||||||
setClearing(true);
|
|
||||||
} catch {
|
|
||||||
setError('Internal server error. Please try again later.');
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}, [file, validateFile]);
|
|
||||||
|
|
||||||
const handleUpload = useCallback(() => {
|
|
||||||
setLoading(true);
|
|
||||||
setError(undefined);
|
|
||||||
setParsedText('');
|
|
||||||
|
|
||||||
uploadFile();
|
|
||||||
}, [uploadFile]);
|
|
||||||
|
|
||||||
function renderForm() {
|
|
||||||
if (clearing) {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<p>File: {file?.name}</p>
|
|
||||||
<Button label='Use a different file' onClick={handleClear} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<input type='file' accept='.pdf' onChange={handleFileChange} />
|
|
||||||
<Button
|
|
||||||
label='Upload and parse'
|
|
||||||
onClick={handleUpload}
|
|
||||||
disabled={!file}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='dashboard'>
|
<div className='dashboard'>
|
||||||
<p>This is a tool to extract the text from a PDF file</p>
|
<p>This is a tool to extract the text from a PDF file</p>
|
||||||
<div className='form'> {renderForm()}</div>
|
<Form
|
||||||
|
setLoading={setLoading}
|
||||||
|
setError={setError}
|
||||||
|
setParsedText={setParsedText}
|
||||||
|
/>
|
||||||
<Content loading={loading} error={error} text={parsedText} />
|
<Content loading={loading} error={error} text={parsedText} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
112
components/Form.tsx
Normal file
112
components/Form.tsx
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
import { responseSchema } from '@/utils/data';
|
||||||
|
import axios from 'axios';
|
||||||
|
import { useCallback, useState } from 'react';
|
||||||
|
import { Button } from './Button';
|
||||||
|
|
||||||
|
interface FormProps {
|
||||||
|
setLoading: (loading: boolean) => void;
|
||||||
|
setError: (error: string | undefined) => void;
|
||||||
|
setParsedText: (parsedText: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Form({ setLoading, setError, setParsedText }: FormProps) {
|
||||||
|
const [clearing, setClearing] = useState(false);
|
||||||
|
const [file, setFile] = useState<File>();
|
||||||
|
|
||||||
|
const handleFileChange = useCallback(
|
||||||
|
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
if (e.target.files) {
|
||||||
|
setFile(e.target.files[0]);
|
||||||
|
} else {
|
||||||
|
setFile(undefined);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleClear = useCallback(() => {
|
||||||
|
setClearing(false);
|
||||||
|
setFile(undefined);
|
||||||
|
setParsedText('');
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const validateFile = useCallback((file: File) => {
|
||||||
|
if (file.type !== 'application/pdf') {
|
||||||
|
setError(
|
||||||
|
"The file you've selected is not a PDF file. Please select a PDF file."
|
||||||
|
);
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This limit is caused by the project being deployed on Vercel using a free tier, with limited resources for the cloud functions (it can be commented out if running locally)
|
||||||
|
if (file.size > 1024 * 1024) {
|
||||||
|
setError('The file must be smaller than 1MB.');
|
||||||
|
setLoading(false);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const uploadFile = useCallback(async () => {
|
||||||
|
if (!file) return;
|
||||||
|
|
||||||
|
if (!validateFile(file)) return;
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
|
||||||
|
formData.append('File', file);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post('/api/parse', formData);
|
||||||
|
const validatedData = responseSchema.safeParse(response.data);
|
||||||
|
|
||||||
|
if (!validatedData.success) {
|
||||||
|
setError('There was an error parsing the file. Please try again.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setParsedText(validatedData.data.text);
|
||||||
|
} catch {
|
||||||
|
setError('Internal server error. Please try again later.');
|
||||||
|
} finally {
|
||||||
|
setClearing(true);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, [file, validateFile]);
|
||||||
|
|
||||||
|
const handleUpload = useCallback(() => {
|
||||||
|
setLoading(true);
|
||||||
|
setError(undefined);
|
||||||
|
setParsedText('');
|
||||||
|
|
||||||
|
uploadFile();
|
||||||
|
}, [uploadFile]);
|
||||||
|
|
||||||
|
function renderForm() {
|
||||||
|
if (clearing) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p>File: {file?.name}</p>
|
||||||
|
<Button label='Use a different file' onClick={handleClear} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<input type='file' accept='.pdf' onChange={handleFileChange} />
|
||||||
|
<Button
|
||||||
|
label='Upload and parse'
|
||||||
|
onClick={handleUpload}
|
||||||
|
disabled={!file}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className='form'> {renderForm()}</div>;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user