- Fixed LoginResponse: added expires_in field - Renamed format to file_format across FileInfo interface - Updated ProcessRequest: replaced confidence_threshold with detect_layout - Modified ProcessResponse: added message and total_files, removed task_id - Removed non-existent getTaskStatus API call - Fixed getOCRResult parameter from taskId to fileId - Commented out translation config APIs pending backend implementation Documentation: - Added API_REFERENCE.md: Complete API endpoint inventory - Added API_FIX_SUMMARY.md: Detailed before/after comparison of all fixes - Added FRONTEND_API.md: Frontend integration documentation - Added FRONTEND_QUICK_START.md: Quick start guide - Added FRONTEND_UPGRADE_SUMMARY.md: Upgrade summary 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
22 KiB
Tool_OCR Frontend API Documentation
Version: 0.1.0 Last Updated: 2025-01-13 Purpose: Complete documentation of frontend architecture, component structure, API integration, and dependencies
Table of Contents
- Project Overview
- Technology Stack
- Component Architecture
- Page → API Dependency Matrix
- Component Tree Structure
- State Management Strategy
- Route Configuration
- API Integration Patterns
- UI/UX Design System
- Error Handling Patterns
- Deployment Configuration
Project Overview
Tool_OCR 前端是一個基於 React 18 + Vite 的現代化 OCR 文件處理系統,提供企業級的使用者介面和體驗。
Key Features
- 批次檔案上傳: 支援拖放上傳,多檔案批次處理
- 即時進度追蹤: 使用輪詢機制顯示 OCR 處理進度
- 結果預覽: Markdown 和 JSON 雙格式預覽
- 靈活匯出: 支援 TXT、JSON、Excel、Markdown、PDF、ZIP 多種格式
- 規則管理: 可自訂匯出規則和 CSS 模板
- 響應式設計: 適配桌面和平板裝置
Technology Stack
Core Dependencies
{
"@tanstack/react-query": "^5.90.7", // Server state management
"react": "^19.2.0", // UI framework
"react-dom": "^19.2.0",
"react-router-dom": "^7.9.5", // Routing
"vite": "^7.2.2", // Build tool
"typescript": "~5.9.3" // Type safety
}
UI & Styling
{
"tailwindcss": "^4.1.17", // CSS framework
"class-variance-authority": "^0.7.0", // Component variants
"clsx": "^2.1.1", // Class name utility
"tailwind-merge": "^3.4.0", // Tailwind class merge
"lucide-react": "^0.553.0" // Icon library
}
State & Data
{
"zustand": "^5.0.8", // Client state
"axios": "^1.13.2", // HTTP client
"react-dropzone": "^14.3.8", // File upload
"react-markdown": "^9.0.1" // Markdown rendering
}
Internationalization
{
"i18next": "^25.6.2",
"react-i18next": "^16.3.0"
}
Component Architecture
Atomic Design Structure
frontend/src/
├── components/
│ ├── ui/ # Atomic components (shadcn/ui)
│ │ ├── button.tsx
│ │ ├── card.tsx
│ │ ├── input.tsx
│ │ ├── label.tsx
│ │ ├── select.tsx
│ │ ├── badge.tsx
│ │ ├── progress.tsx
│ │ ├── alert.tsx
│ │ ├── dialog.tsx
│ │ ├── tabs.tsx
│ │ ├── table.tsx
│ │ └── toast.tsx
│ ├── FileUpload.tsx # Drag-and-drop upload component
│ ├── ResultsTable.tsx # OCR results display table
│ ├── MarkdownPreview.tsx # Markdown content renderer
│ └── Layout.tsx # Main app layout with sidebar
├── pages/
│ ├── LoginPage.tsx # Authentication
│ ├── UploadPage.tsx # File upload and selection
│ ├── ProcessingPage.tsx # OCR processing status
│ ├── ResultsPage.tsx # Results viewing and preview
│ ├── ExportPage.tsx # Export configuration and download
│ └── SettingsPage.tsx # User settings and rules management
├── store/
│ ├── authStore.ts # Authentication state (Zustand)
│ └── uploadStore.ts # Upload batch state (Zustand)
├── services/
│ └── api.ts # API client (Axios)
├── types/
│ └── api.ts # TypeScript type definitions
├── lib/
│ └── utils.ts # Utility functions
├── i18n/
│ └── index.ts # i18n configuration
└── styles/
└── index.css # Global styles and CSS variables
Page → API Dependency Matrix
| Page/Component | API Endpoints Used | HTTP Method | Purpose | Polling |
|---|---|---|---|---|
| LoginPage | /api/v1/auth/login |
POST | User authentication | No |
| UploadPage | /api/v1/upload |
POST | Upload files for OCR | No |
| ProcessingPage | /api/v1/ocr/process |
POST | Start OCR processing | No |
/api/v1/batch/{batch_id}/status |
GET | Poll batch status | Yes (2s) | |
| ResultsPage | /api/v1/batch/{batch_id}/status |
GET | Load completed files | No |
/api/v1/ocr/result/{file_id} |
GET | Get OCR result details | No | |
/api/v1/export/pdf/{file_id} |
GET | Download PDF export | No | |
| ExportPage | /api/v1/export |
POST | Export batch results | No |
/api/v1/export/rules |
GET | List export rules | No | |
/api/v1/export/rules |
POST | Create new rule | No | |
/api/v1/export/rules/{rule_id} |
PUT | Update existing rule | No | |
/api/v1/export/rules/{rule_id} |
DELETE | Delete rule | No | |
/api/v1/export/css-templates |
GET | List CSS templates | No | |
| SettingsPage | /api/v1/export/rules |
GET | Manage export rules | No |
Component Tree Structure
App
├── Router (React Router)
│ ├── PublicRoute
│ │ └── LoginPage
│ │ ├── Form (username + password)
│ │ ├── Button (submit)
│ │ └── Alert (error display)
│ └── ProtectedRoute (requires authentication)
│ └── Layout
│ ├── Sidebar
│ │ ├── Logo
│ │ ├── Navigation Links
│ │ │ ├── UploadPage link
│ │ │ ├── ProcessingPage link
│ │ │ ├── ResultsPage link
│ │ │ ├── ExportPage link
│ │ │ └── SettingsPage link
│ │ └── User Section + Logout
│ ├── TopBar
│ │ ├── SearchInput
│ │ └── NotificationBell
│ └── MainContent (Outlet)
│ ├── UploadPage
│ │ ├── FileUpload (react-dropzone)
│ │ ├── FileList (selected files)
│ │ └── UploadButton
│ ├── ProcessingPage
│ │ ├── ProgressBar
│ │ ├── StatsCards (completed/processing/failed)
│ │ ├── FileStatusList
│ │ └── ActionButtons
│ ├── ResultsPage
│ │ ├── FileList (left sidebar)
│ │ │ ├── SearchInput
│ │ │ └── FileItems
│ │ └── PreviewPanel (right)
│ │ ├── StatsCards
│ │ ├── Tabs (Markdown/JSON)
│ │ ├── MarkdownPreview
│ │ └── JSONViewer
│ ├── ExportPage
│ │ ├── FormatSelector
│ │ ├── RuleSelector
│ │ ├── CSSTemplateSelector
│ │ ├── OptionsForm
│ │ └── ExportButton
│ └── SettingsPage
│ ├── UserInfo
│ ├── ExportRulesManager
│ │ ├── RuleList
│ │ ├── CreateRuleDialog
│ │ ├── EditRuleDialog
│ │ └── DeleteConfirmDialog
│ └── SystemSettings
State Management Strategy
Client State (Zustand)
authStore.ts - Authentication State
interface AuthState {
user: User | null
isAuthenticated: boolean
setUser: (user: User | null) => void
logout: () => void
}
uploadStore.ts - Upload Batch State
interface UploadState {
batchId: number | null
files: FileInfo[]
uploadProgress: number
setBatchId: (id: number) => void
setFiles: (files: FileInfo[]) => void
setUploadProgress: (progress: number) => void
reset: () => void
}
Server State (React Query)
- Caching: Automatic caching with stale-while-revalidate strategy
- Polling: Automatic refetch for batch status every 2 seconds during processing
- Error Handling: Built-in error retry and error state management
- Optimistic Updates: For export rules CRUD operations
Query Keys
// Batch status polling
['batchStatus', batchId]
// OCR result for specific file
['ocrResult', fileId]
// Export rules list
['exportRules']
// CSS templates list
['cssTemplates']
Route Configuration
| Route | Component | Access Level | Description | Protected |
|---|---|---|---|---|
/login |
LoginPage | Public | User authentication | No |
/ |
Layout (redirect to /upload) | Private | Main layout wrapper | Yes |
/upload |
UploadPage | Private | File upload interface | Yes |
/processing |
ProcessingPage | Private | OCR processing status | Yes |
/results |
ResultsPage | Private | View OCR results | Yes |
/export |
ExportPage | Private | Export configuration | Yes |
/settings |
SettingsPage | Private | User settings | Yes |
Protected Route Implementation
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const isAuthenticated = useAuthStore((state) => state.isAuthenticated)
if (!isAuthenticated) {
return <Navigate to="/login" replace />
}
return <>{children}</>
}
API Integration Patterns
API Client Configuration
Base URL: http://localhost:12010/api/v1
Request Interceptor: Adds JWT token to Authorization header
this.client.interceptors.request.use((config) => {
if (this.token) {
config.headers.Authorization = `Bearer ${this.token}`
}
return config
})
Response Interceptor: Handles 401 errors and redirects to login
this.client.interceptors.response.use(
(response) => response,
(error: AxiosError<ApiError>) => {
if (error.response?.status === 401) {
this.clearToken()
window.location.href = '/login'
}
return Promise.reject(error)
}
)
Authentication Flow
// 1. Login
const response = await apiClient.login({ username, password })
// Response: { access_token, token_type, expires_in }
// 2. Store token
localStorage.setItem('auth_token', response.access_token)
// 3. Set user in store
setUser({ id: 1, username })
// 4. Navigate to /upload
navigate('/upload')
File Upload Flow
// 1. Prepare FormData
const formData = new FormData()
files.forEach((file) => formData.append('files', file))
// 2. Upload files
const response = await apiClient.uploadFiles(files)
// Response: { batch_id, files: FileInfo[] }
// 3. Store batch info
setBatchId(response.batch_id)
setFiles(response.files)
// 4. Navigate to /processing
navigate('/processing')
OCR Processing Flow
// 1. Start OCR processing
await apiClient.processOCR({ batch_id, lang: 'ch', detect_layout: true })
// Response: { message, batch_id, total_files, status }
// 2. Poll batch status every 2 seconds
const { data: batchStatus } = useQuery({
queryKey: ['batchStatus', batchId],
queryFn: () => apiClient.getBatchStatus(batchId),
refetchInterval: (query) => {
const status = query.state.data?.batch.status
if (status === 'completed' || status === 'failed') return false
return 2000 // Poll every 2 seconds
},
})
// 3. Auto-redirect when completed
useEffect(() => {
if (batchStatus?.batch.status === 'completed') {
navigate('/results')
}
}, [batchStatus?.batch.status])
Results Viewing Flow
// 1. Load batch status
const { data: batchStatus } = useQuery({
queryKey: ['batchStatus', batchId],
queryFn: () => apiClient.getBatchStatus(batchId),
})
// 2. Select a file
setSelectedFileId(fileId)
// 3. Load OCR result for selected file
const { data: ocrResult } = useQuery({
queryKey: ['ocrResult', selectedFileId],
queryFn: () => apiClient.getOCRResult(selectedFileId),
enabled: !!selectedFileId,
})
// 4. Display in Markdown or JSON format
<Tabs>
<TabsContent value="markdown">
<ReactMarkdown>{ocrResult.markdown_content}</ReactMarkdown>
</TabsContent>
<TabsContent value="json">
<pre>{JSON.stringify(ocrResult.json_data, null, 2)}</pre>
</TabsContent>
</Tabs>
Export Flow
// 1. Select export format and options
const exportData = {
batch_id: batchId,
format: 'pdf',
rule_id: selectedRuleId,
css_template: 'academic',
options: { include_metadata: true }
}
// 2. Request export
const blob = await apiClient.exportResults(exportData)
// 3. Trigger download
downloadBlob(blob, `ocr-results-${batchId}.pdf`)
UI/UX Design System
Color Palette (CSS Variables)
/* Primary - Professional Blue */
--primary: 217 91% 60%; /* #3b82f6 */
--primary-foreground: 0 0% 100%;
/* Secondary - Gray-Blue */
--secondary: 220 15% 95%;
--secondary-foreground: 220 15% 25%;
/* Accent - Vibrant Teal */
--accent: 173 80% 50%;
--accent-foreground: 0 0% 100%;
/* Success */
--success: 142 72% 45%; /* #16a34a */
--success-foreground: 0 0% 100%;
/* Destructive */
--destructive: 0 85% 60%; /* #ef4444 */
--destructive-foreground: 0 0% 100%;
/* Warning */
--warning: 38 92% 50%;
--warning-foreground: 0 0% 100%;
/* Background */
--background: 220 15% 97%; /* #fafafa */
--card: 0 0% 100%; /* #ffffff */
--sidebar: 220 25% 12%; /* Dark blue-gray */
/* Borders */
--border: 220 13% 88%;
--radius: 0.5rem;
Typography
- Font Family: System font stack (native)
- Page Title: 1.875rem (30px), font-weight: 700
- Section Title: 1.125rem (18px), font-weight: 600
- Body Text: 0.875rem (14px), font-weight: 400
- Small Text: 0.75rem (12px)
Spacing Scale
--spacing-xs: 0.25rem; /* 4px */
--spacing-sm: 0.5rem; /* 8px */
--spacing-md: 1rem; /* 16px */
--spacing-lg: 1.5rem; /* 24px */
--spacing-xl: 2rem; /* 32px */
Component Variants
Button Variants:
default: Primary blue backgroundoutline: Border onlysecondary: Muted backgrounddestructive: Red for delete actionsghost: No background, hover effect
Alert Variants:
default: Neutral grayinfo: Bluesuccess: Greenwarning: Yellowdestructive: Red
Badge Variants:
default: Graysuccess: Greenwarning: Yellowdestructive: Redsecondary: Muted
Responsive Breakpoints
// Tailwind breakpoints
sm: '640px', // Mobile landscape
md: '768px', // Tablet
lg: '1024px', // Desktop (primary support)
xl: '1280px', // Large desktop
2xl: '1536px' // Extra large
Primary Support: Desktop (>= 1024px) Secondary Support: Tablet (768px - 1023px) Optional: Mobile (< 768px)
Error Handling Patterns
Global Error Boundary
class ErrorBoundary extends Component<Props, State> {
static getDerivedStateFromError(error: Error): State {
return { hasError: true, error }
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error('Uncaught error:', error, errorInfo)
}
render() {
if (this.state.hasError) {
return <ErrorFallbackUI error={this.state.error} />
}
return this.props.children
}
}
API Error Handling
try {
await apiClient.uploadFiles(files)
} catch (err: any) {
const errorDetail = err.response?.data?.detail
toast({
title: t('upload.uploadError'),
description: Array.isArray(errorDetail)
? errorDetail.map(e => e.msg || e.message).join(', ')
: errorDetail || t('errors.networkError'),
variant: 'destructive',
})
}
Form Validation
// Client-side validation
if (selectedFiles.length === 0) {
toast({
title: t('errors.validationError'),
description: '請選擇至少一個檔案',
variant: 'destructive',
})
return
}
// Backend validation errors
if (err.response?.status === 422) {
const errors = err.response.data.detail
// Display validation errors to user
}
Loading States
// Query loading state
const { data, isLoading, error } = useQuery({
queryKey: ['batchStatus', batchId],
queryFn: () => apiClient.getBatchStatus(batchId),
})
if (isLoading) return <LoadingSpinner />
if (error) return <ErrorAlert error={error} />
if (!data) return <EmptyState />
// Mutation loading state
const mutation = useMutation({
mutationFn: apiClient.uploadFiles,
onSuccess: () => { /* success */ },
onError: () => { /* error */ },
})
<Button disabled={mutation.isPending}>
{mutation.isPending ? <Loader2 className="animate-spin" /> : '上傳'}
</Button>
Deployment Configuration
Environment Variables
# .env.production
VITE_API_BASE_URL=http://localhost:12010
VITE_APP_NAME=Tool_OCR
VITE_APP_VERSION=0.1.0
Build Configuration
vite.config.ts:
export default defineConfig({
plugins: [react()],
server: {
port: 12011,
proxy: {
'/api': {
target: 'http://localhost:12010',
changeOrigin: true,
},
},
},
build: {
outDir: 'dist',
sourcemap: false,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom', 'react-router-dom'],
ui: ['@tanstack/react-query', 'zustand', 'lucide-react'],
},
},
},
},
})
Build Commands
# Development
npm run dev
# Production build
npm run build
# Preview production build
npm run preview
Nginx Configuration
server {
listen 80;
server_name tool-ocr.example.com;
root /path/to/Tool_OCR/frontend/dist;
# Frontend static files
location / {
try_files $uri $uri/ /index.html;
}
# API reverse proxy
location /api {
proxy_pass http://127.0.0.1:12010;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# Static assets caching
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
Performance Optimization
Code Splitting
- Vendor Bundle: React, React Router, React Query (separate chunk)
- UI Bundle: Zustand, Lucide React, UI components
- Route-based Splitting: Lazy load pages with
React.lazy()
Caching Strategy
- React Query Cache: 5 minutes stale time for most queries
- Polling Interval: 2 seconds during OCR processing
- Infinite Cache: Export rules (rarely change)
Asset Optimization
- Images: Convert to WebP format, use appropriate sizes
- Fonts: System font stack (no custom fonts)
- Icons: Lucide React (tree-shakeable)
Testing Strategy
Component Testing (Planned)
// Example: UploadPage.test.tsx
import { render, screen, fireEvent } from '@testing-library/react'
import { UploadPage } from '@/pages/UploadPage'
describe('UploadPage', () => {
it('should display file upload area', () => {
render(<UploadPage />)
expect(screen.getByText(/拖放檔案/i)).toBeInTheDocument()
})
it('should allow file selection', async () => {
render(<UploadPage />)
const file = new File(['content'], 'test.pdf', { type: 'application/pdf' })
// Test file upload
})
})
API Integration Testing
- Mock API Responses: Use MSW (Mock Service Worker)
- Error Scenarios: Test 401, 404, 500 responses
- Loading States: Test skeleton/spinner display
Accessibility Standards
WCAG 2.1 AA Compliance
- Keyboard Navigation: All interactive elements accessible via keyboard
- Focus Indicators: Visible focus states on all inputs and buttons
- ARIA Labels: Proper labels for screen readers
- Color Contrast: Minimum 4.5:1 ratio for text
- Alt Text: All images have descriptive alt attributes
Semantic HTML
// Use semantic elements
<nav> // Navigation
<main> // Main content
<aside> // Sidebar
<article> // Independent content
<section> // Grouped content
Browser Compatibility
Minimum Supported Versions
- Chrome: 90+
- Firefox: 88+
- Edge: 90+
- Safari: 14+
Polyfills Required
- None (modern build target: ES2020)
Development Workflow
Local Development
# 1. Install dependencies
npm install
# 2. Start dev server
npm run dev
# Frontend: http://localhost:12011
# API Proxy: http://localhost:12011/api -> http://localhost:12010/api
# 3. Build for production
npm run build
# 4. Preview production build
npm run preview
Code Style
- Formatter: Prettier (automatic on save)
- Linter: ESLint
- Type Checking: TypeScript strict mode
Known Issues & Limitations
Current Limitations
- No Real-time WebSocket: Uses HTTP polling for progress updates
- No Offline Support: Requires active internet connection
- No Mobile Optimization: Primarily designed for desktop/tablet
- Translation Feature Stub: Planned for Phase 5
- File Size Limit: Frontend validates 50MB per file, backend may differ
Future Improvements
- Implement WebSocket for real-time updates
- Add dark mode toggle
- Mobile responsive design
- Implement translation feature
- Add E2E tests with Playwright
- PWA support for offline capability
Maintenance & Updates
Update Checklist
When updating API contracts:
- Update TypeScript types in
@/types/api.ts - Update API client methods in
@/services/api.ts - Update this documentation (FRONTEND_API.md)
- Update corresponding page components
- Test integration thoroughly
Dependency Updates
# Check for updates
npm outdated
# Update dependencies
npm update
# Update to latest (breaking changes possible)
npm install <package>@latest
Contact & Support
Frontend Developer: Claude Code Documentation Version: 0.1.0 Last Updated: 2025-01-13
For API questions, refer to:
API_REFERENCE.md- Complete API documentationbackend_api.md- Backend implementation details- FastAPI Swagger UI:
http://localhost:12010/docs
End of Documentation