Files
Nils-Johan Gynther e18bf79395 feat: Implement PDF document import functionality with Markdown conversion
- Added DocumentImportModule, DocumentImportController, and DocumentImportService for handling PDF uploads.
- Integrated pdf-parse for extracting text from PDF files.
- Created PdfParser for parsing PDF documents and converting them to Markdown format.
- Updated frontend to support file uploads via drag-and-drop and file input for PDF documents.
- Modified API routes to handle document import requests.
- Enhanced error handling for unsupported file types and file size limits.
- Updated README to reflect new features and usage instructions.
2026-04-12 18:57:40 +02:00

249 lines
7.5 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useRef, useState } from 'react';
import Navigation from '../Navigation';
import { parseErrorResponse } from '../../lib/error-handler';
interface ImportResult {
markdown: string;
title: string;
documentType: 'pdf';
metadata?: {
pageCount?: number;
producer?: string;
creationDate?: string;
characterCount?: number;
};
}
export default function ImportPage() {
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [isDragging, setIsDragging] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [result, setResult] = useState<ImportResult | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const handleFileSelect = (file: File) => {
setError(null);
setResult(null);
if (!file.name.toLowerCase().endsWith('.pdf')) {
setError('Endast PDF-filer stöds för tillfället.');
return;
}
setSelectedFile(file);
};
const handleDragOver = (e: React.DragEvent) => {
e.preventDefault();
setIsDragging(true);
};
const handleDragLeave = () => setIsDragging(false);
const handleDrop = (e: React.DragEvent) => {
e.preventDefault();
setIsDragging(false);
const file = e.dataTransfer.files[0];
if (file) handleFileSelect(file);
};
const handleFileInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) handleFileSelect(file);
};
const handleImport = async (e: React.FormEvent) => {
e.preventDefault();
if (!selectedFile) return;
setError(null);
setResult(null);
setIsLoading(true);
try {
const formData = new FormData();
formData.append('file', selectedFile);
const res = await fetch('/api/document-import-proxy', {
method: 'POST',
body: formData,
});
if (!res.ok) {
const errorMessage = await parseErrorResponse(res);
setError(errorMessage || 'Importen misslyckades.');
return;
}
const data: ImportResult = await res.json();
setResult(data);
} catch (err) {
const message = err instanceof Error ? err.message : 'Något oväntat gick fel';
setError(`Fel: ${message}`);
} finally {
setIsLoading(false);
}
};
const copyToClipboard = () => {
if (result?.markdown) {
navigator.clipboard.writeText(result.markdown);
}
};
return (
<main style={{ padding: '1rem', maxWidth: '900px', margin: '0 auto' }}>
<Navigation />
<h1 style={{ marginBottom: '0.5rem' }}>Importera dokument</h1>
<p style={{ margin: '0 0 1.5rem 0', color: '#6b7280', fontSize: '0.95rem' }}>
Ladda upp en PDF-fil och konvertera den till Markdown-format.
</p>
{/* UPLOAD-SEKTION */}
<form onSubmit={handleImport}>
<div
onDragOver={handleDragOver}
onDragLeave={handleDragLeave}
onDrop={handleDrop}
onClick={() => fileInputRef.current?.click()}
style={{
border: `2px dashed ${isDragging ? '#3b82f6' : selectedFile ? '#10b981' : '#d1d5db'}`,
borderRadius: '8px',
padding: '2.5rem 1.5rem',
textAlign: 'center',
cursor: 'pointer',
background: isDragging ? '#eff6ff' : selectedFile ? '#f0fdf4' : '#f9fafb',
transition: 'all 0.15s ease',
marginBottom: '1rem',
}}
>
<input
ref={fileInputRef}
type="file"
accept=".pdf,application/pdf"
onChange={handleFileInputChange}
style={{ display: 'none' }}
/>
<div style={{ fontSize: '2.5rem', marginBottom: '0.5rem' }}>
{selectedFile ? '📄' : '⬆️'}
</div>
{selectedFile ? (
<>
<p style={{ margin: '0 0 0.25rem 0', fontWeight: 600, color: '#065f46' }}>
{selectedFile.name}
</p>
<p style={{ margin: 0, fontSize: '0.85rem', color: '#6b7280' }}>
{(selectedFile.size / 1024 / 1024).toFixed(2)} MB Klicka för att byta fil
</p>
</>
) : (
<>
<p style={{ margin: '0 0 0.25rem 0', fontWeight: 600, color: '#374151' }}>
Dra och släpp din PDF här
</p>
<p style={{ margin: 0, fontSize: '0.85rem', color: '#6b7280' }}>
eller klicka för att välja fil (max 50 MB)
</p>
</>
)}
</div>
{error && (
<p
style={{
margin: '0 0 1rem 0',
color: '#991b1b',
background: '#fee2e2',
padding: '0.75rem',
borderRadius: '4px',
fontSize: '0.85rem',
}}
>
{error}
</p>
)}
<button
type="submit"
disabled={isLoading || !selectedFile}
style={{
padding: '0.75rem 2rem',
background: '#3b82f6',
color: '#fff',
border: 'none',
borderRadius: '4px',
cursor: isLoading || !selectedFile ? 'not-allowed' : 'pointer',
opacity: isLoading || !selectedFile ? 0.5 : 1,
fontSize: '0.95rem',
fontWeight: 600,
}}
>
{isLoading ? 'Konverterar...' : 'Konvertera till Markdown'}
</button>
</form>
{/* RESULT */}
{result && (
<div
style={{
marginTop: '2rem',
background: '#f0fdf4',
border: '2px solid #10b981',
borderRadius: '8px',
padding: '1.5rem',
}}
>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
<h2 style={{ margin: 0, color: '#065f46' }}> {result.title}</h2>
<button
onClick={copyToClipboard}
style={{
padding: '0.4rem 0.9rem',
background: '#fff',
border: '1px solid #10b981',
borderRadius: '4px',
cursor: 'pointer',
fontSize: '0.85rem',
color: '#065f46',
}}
>
Kopiera Markdown
</button>
</div>
{result.metadata && (
<p style={{ margin: '0 0 1rem 0', fontSize: '0.85rem', color: '#6b7280' }}>
{result.metadata.pageCount} sidor
{result.metadata.characterCount ? ` · ${result.metadata.characterCount.toLocaleString('sv')} tecken` : ''}
</p>
)}
<div
style={{
background: '#fff',
border: '1px solid #d1fae5',
borderRadius: '4px',
padding: '1rem',
maxHeight: '500px',
overflowY: 'auto',
}}
>
<pre
style={{
margin: 0,
fontSize: '0.85rem',
wordBreak: 'break-word',
whiteSpace: 'pre-wrap',
overflowWrap: 'break-word',
}}
>
{result.markdown}
</pre>
</div>
</div>
)}
</main>
);
}