IDP-Machine-learning / nextjs_integration_guide.md
mrrobot2610's picture
Initial commit: IDP (Intelligent Document Processing) System
1a7ee60
|
Raw History Blame Contribute Delete
11.7 kB
# Next.js Integration Guide
Guide for integrating the IDP API with your Next.js frontend hosted on Vercel.
## Overview
The IDP API provides a RESTful interface for document processing. Your Next.js app will:
1. Allow users to upload documents (PDF/images)
2. Send files to the API via POST request
3. Display extracted document data with confidence scores
4. Handle loading states and errors
## API Endpoints
### Base URL
```
https://YOUR_USERNAME-idp-api.hf.space
```
Replace `YOUR_USERNAME-idp-api` with your actual Space name.
### Endpoints
#### GET /health
Health check endpoint.
**Response:**
```json
{
"status": "ok",
"models_loaded": true,
"version": "1.0.0"
}
```
#### POST /process
Process a document and extract structured data.
**Request:**
- Method: `POST`
- Content-Type: `multipart/form-data`
- Body: `file` (PDF or image file)
- Optional params:
- `adaptive_threshold`: boolean (default: false)
- `page_number`: number (for PDFs, process specific page)
**Response:**
```typescript
{
file_type: "image" | "pdf";
total_pages: number;
processed_pages: number;
filename: string;
file_size_kb: number;
pages: Array<{
document_type: "INVOICE" | "RECEIPT" | "FORM" | "OTHER";
classification_confidence: number;
classification_probabilities: {
INVOICE: number;
RECEIPT: number;
FORM: number;
OTHER: number;
};
fields: {
[key: string]: {
value: string;
confidence: number;
bbox?: [number, number, number, number];
source: "ner" | "regex";
normalized?: boolean;
currency?: string;
numeric_value?: number;
};
};
processing_time: {
preprocessing: number;
ocr: number;
classification: number;
ner: number;
postprocessing: number;
total: number;
};
raw_ocr_text: string;
page_number?: number;
}>;
}
```
## Implementation
### 1. Create TypeScript Types
Create `types/idp.ts`:
```typescript
export interface IDPField {
value: string;
confidence: number;
bbox?: [number, number, number, number];
source: "ner" | "regex";
normalized?: boolean;
currency?: string;
numeric_value?: number;
}
export interface IDPPage {
document_type: "INVOICE" | "RECEIPT" | "FORM" | "OTHER";
classification_confidence: number;
classification_probabilities: {
INVOICE: number;
RECEIPT: number;
FORM: number;
OTHER: number;
};
fields: Record<string, IDPField>;
processing_time: {
preprocessing: number;
ocr: number;
classification: number;
ner: number;
postprocessing: number;
total: number;
};
raw_ocr_text: string;
page_number?: number;
}
export interface IDPResponse {
file_type: "image" | "pdf";
total_pages: number;
processed_pages: number;
filename: string;
file_size_kb: number;
pages: IDPPage[];
}
export interface HealthResponse {
status: string;
models_loaded: boolean;
version: string;
}
```
### 2. Create API Client
Create `lib/idp-api.ts`:
```typescript
const API_BASE_URL = process.env.NEXT_PUBLIC_IDP_API_URL ||
'https://YOUR_USERNAME-idp-api.hf.space';
export class IDPAPIError extends Error {
constructor(
message: string,
public statusCode?: number,
public details?: any
) {
super(message);
this.name = 'IDPAPIError';
}
}
export async function checkHealth(): Promise<HealthResponse> {
try {
const response = await fetch(`${API_BASE_URL}/health`);
if (!response.ok) {
throw new IDPAPIError(
'Health check failed',
response.status
);
}
return await response.json();
} catch (error) {
if (error instanceof IDPAPIError) throw error;
throw new IDPAPIError('Network error: ' + (error as Error).message);
}
}
export async function processDocument(
file: File,
options?: {
adaptiveThreshold?: boolean;
pageNumber?: number;
onProgress?: (progress: number) => void;
}
): Promise<IDPResponse> {
const formData = new FormData();
formData.append('file', file);
if (options?.adaptiveThreshold !== undefined) {
formData.append('adaptive_threshold', String(options.adaptiveThreshold));
}
if (options?.pageNumber !== undefined) {
formData.append('page_number', String(options.pageNumber));
}
try {
const response = await fetch(`${API_BASE_URL}/process`, {
method: 'POST',
body: formData,
});
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new IDPAPIError(
error.detail || 'Document processing failed',
response.status,
error
);
}
return await response.json();
} catch (error) {
if (error instanceof IDPAPIError) throw error;
throw new IDPAPIError('Network error: ' + (error as Error).message);
}
}
```
### 3. Create Document Upload Component
Create `components/DocumentUpload.tsx`:
```typescript
'use client';
import { useState, useCallback } from 'react';
import { processDocument, IDPResponse, IDPAPIError } from '@/lib/idp-api';
export default function DocumentUpload() {
const [file, setFile] = useState<File | null>(null);
const [loading, setLoading] = useState(false);
const [result, setResult] = useState<IDPResponse | null>(null);
const [error, setError] = useState<string | null>(null);
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const selectedFile = e.target.files?.[0];
if (selectedFile) {
setFile(selectedFile);
setResult(null);
setError(null);
}
};
const handleUpload = useCallback(async () => {
if (!file) return;
setLoading(true);
setError(null);
try {
const response = await processDocument(file);
setResult(response);
} catch (err) {
if (err instanceof IDPAPIError) {
setError(err.message);
} else {
setError('An unexpected error occurred');
}
} finally {
setLoading(false);
}
}, [file]);
return (
<div className="max-w-4xl mx-auto p-6">
<h1 className="text-3xl font-bold mb-6">Document Processing</h1>
{/* File Upload */}
<div className="mb-6">
<label className="block mb-2 font-medium">
Upload Document (PDF or Image)
</label>
<input
type="file"
accept=".pdf,.png,.jpg,.jpeg"
onChange={handleFileChange}
className="block w-full text-sm text-gray-900 border border-gray-300 rounded-lg cursor-pointer"
/>
</div>
{/* Upload Button */}
<button
onClick={handleUpload}
disabled={!file || loading}
className="px-6 py-2 bg-blue-600 text-white rounded-lg disabled:bg-gray-400 disabled:cursor-not-allowed"
>
{loading ? 'Processing...' : 'Process Document'}
</button>
{/* Error Display */}
{error && (
<div className="mt-6 p-4 bg-red-50 border border-red-200 rounded-lg">
<p className="text-red-800">{error}</p>
</div>
)}
{/* Results Display */}
{result && (
<div className="mt-6 space-y-4">
<h2 className="text-2xl font-semibold">Results</h2>
{result.pages.map((page, idx) => (
<div key={idx} className="border rounded-lg p-4">
<div className="mb-4">
<span className="font-medium">Document Type:</span>{' '}
<span className="px-3 py-1 bg-blue-100 text-blue-800 rounded-full text-sm">
{page.document_type}
</span>
<span className="ml-3 text-sm text-gray-600">
Confidence: {(page.classification_confidence * 100).toFixed(1)}%
</span>
</div>
<h3 className="font-semibold mb-2">Extracted Fields:</h3>
<div className="space-y-2">
{Object.entries(page.fields).map(([key, field]) => (
<div key={key} className="flex justify-between p-2 bg-gray-50 rounded">
<span className="font-medium capitalize">
{key.replace(/_/g, ' ')}:
</span>
<div className="text-right">
<div>{field.value}</div>
<div className="text-xs text-gray-500">
Confidence: {(field.confidence * 100).toFixed(1)}%
</div>
</div>
</div>
))}
</div>
<div className="mt-4 text-sm text-gray-600">
Processing time: {page.processing_time.total.toFixed(2)}s
</div>
</div>
))}
</div>
)}
</div>
);
}
```
### 4. Environment Variables
Create `.env.local`:
```env
NEXT_PUBLIC_IDP_API_URL=https://YOUR_USERNAME-idp-api.hf.space
```
### 5. Add to Your Page
In `app/page.tsx`:
```typescript
import DocumentUpload from '@/components/DocumentUpload';
export default function Home() {
return (
<main>
<DocumentUpload />
</main>
);
}
```
## Advanced Features
### Health Check on Mount
```typescript
useEffect(() => {
checkHealth()
.then(health => {
if (!health.models_loaded) {
setWarning('API is starting up, please wait...');
}
})
.catch(() => setError('API is unavailable'));
}, []);
```
### File Preview
```typescript
const [preview, setPreview] = useState<string | null>(null);
useEffect(() => {
if (file && file.type.startsWith('image/')) {
const url = URL.createObjectURL(file);
setPreview(url);
return () => URL.revokeObjectURL(url);
}
}, [file]);
```
### Progress Indicator
```typescript
const [progress, setProgress] = useState(0);
await processDocument(file, {
onProgress: (p) => setProgress(p)
});
```
## Deployment to Vercel
1. Push your Next.js code to GitHub
2. Go to [Vercel](https://vercel.com)
3. Import your repository
4. Add environment variable:
- `NEXT_PUBLIC_IDP_API_URL`: Your HF Space URL
5. Deploy
## Error Handling
Common errors and solutions:
| Error | Cause | Solution |
|-------|-------|----------|
| CORS error | API not allowing origin | Check CORS config in `api_server.py` |
| 503 Service Unavailable | Models not loaded | Wait for API to finish starting |
| 413 Payload Too Large | File > 10MB | Compress file or increase limit |
| 400 Unsupported file type | Wrong file format | Check file extension |
## Performance Tips
- **Debounce uploads**: Prevent rapid re-uploads
- **Show progress**: Use loading states for better UX
- **Validate files**: Check size/type before uploading
- **Cache results**: Store processed documents if re-processing same file
- **Error boundaries**: Wrap component in error boundary
## Example: Full Featured Component
See `components/AdvancedDocumentUpload.tsx` in the example repository for a production-ready component with:
- Drag & drop support
- File preview
- Progress tracking
- Error handling
- Result visualization
- Download processed JSON
## Support
For integration issues:
- Check browser console for errors
- Test API directly with `curl` first
- Verify CORS configuration
- Check network tab in DevTools