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:

{
  "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:

{
  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:

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:

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:

'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:

NEXT_PUBLIC_IDP_API_URL=https://YOUR_USERNAME-idp-api.hf.space

5. Add to Your Page

In app/page.tsx:

import DocumentUpload from '@/components/DocumentUpload';

export default function Home() {
  return (
    <main>
      <DocumentUpload />
    </main>
  );
}

Advanced Features

Health Check on Mount

useEffect(() => {
  checkHealth()
    .then(health => {
      if (!health.models_loaded) {
        setWarning('API is starting up, please wait...');
      }
    })
    .catch(() => setError('API is unavailable'));
}, []);

File Preview

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

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
  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