| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- /**
- * Processing mode selection component
- * Used to select fast or precise mode when uploading files
- */
- import React, { useState, useEffect } from 'react';
- import { uploadService } from '../services/uploadService';
- import { ModeRecommendation } from '../types';
- interface ModeSelectorProps {
- file: File | null;
- onModeChange: (mode: 'fast' | 'precise') => void;
- className?: string;
- }
- export const ModeSelector: React.FC<ModeSelectorProps> = ({
- file,
- onModeChange,
- className = '',
- }) => {
- const [selectedMode, setSelectedMode] = useState<'fast' | 'precise'>('fast');
- const [recommendation, setRecommendation] = useState<ModeRecommendation | null>(null);
- const [loading, setLoading] = useState(false);
- useEffect(() => {
- if (file) {
- loadRecommendation();
- } else {
- setRecommendation(null);
- }
- }, [file]);
- const loadRecommendation = async () => {
- if (!file) return;
- setLoading(true);
- try {
- const rec = await uploadService.recommendMode(file);
- setRecommendation(rec);
- // Automatically select recommended mode
- setSelectedMode(rec.recommendedMode);
- onModeChange(rec.recommendedMode);
- } catch (error) {
- console.error('Failed to get mode recommendation:', error);
- } finally {
- setLoading(false);
- }
- };
- const handleModeChange = (mode: 'fast' | 'precise') => {
- setSelectedMode(mode);
- onModeChange(mode);
- };
- if (!file) {
- return null;
- }
- return (
- <div className={`mode-selector ${className}`}>
- <div className="mode-selector-header">
- <h4>Select processing mode</h4>
- {loading && <span className="loading">Analyzing...</span>}
- </div>
- {/* Mode recommendation info */}
- {recommendation && (
- <div className="recommendation-info">
- <div className="reason">
- <strong>Recommended:</strong> {recommendation.reason}
- </div>
- {recommendation.warnings && recommendation.warnings.length > 0 && (
- <div className="warnings">
- {recommendation.warnings.map((warning, idx) => (
- <div key={idx} className="warning-item">
- ⚠️ {warning}
- </div>
- ))}
- </div>
- )}
- </div>
- )}
- {/* Mode selection */}
- <div className="mode-options">
- <label className={`mode-option ${selectedMode === 'fast' ? 'selected' : ''}`}>
- <input
- type="radio"
- name="processing-mode"
- value="fast"
- checked={selectedMode === 'fast'}
- onChange={() => handleModeChange('fast')}
- />
- <div className="mode-content">
- <div className="mode-title">⚡ Fast Mode</div>
- <div className="mode-desc">
- Simple text extraction, fast, ideal for plain text documents
- </div>
- <div className="mode-benefits">
- ✅ Fast<br />
- ✅ No additional cost<br />
- ❌ Processes text information only
- </div>
- </div>
- </label>
- <label className={`mode-option ${selectedMode === 'precise' ? 'selected' : ''}`}>
- <input
- type="radio"
- name="processing-mode"
- value="precise"
- checked={selectedMode === 'precise'}
- onChange={() => handleModeChange('precise')}
- />
- <div className="mode-content">
- <div className="mode-title">🎯 Precise Mode</div>
- <div className="mode-desc">
- Accurately recognizes content and retains full information
- </div>
- <div className="mode-benefits">
- ✅ Recognizes images/tables<br />
- ✅ Retains layout information<br />
- ✅ Mixed image and text content<br />
- ⚠️ API cost required<br />
- ⚠️ Long processing time
- </div>
- </div>
- </label>
- </div>
- <style jsx>{`
- .mode-selector {
- margin: 16px 0;
- padding: 16px;
- border: 1px solid #e0e0e0;
- border-radius: 8px;
- background: #fafafa;
- }
- .mode-selector-header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 12px;
- }
- .mode-selector-header h4 {
- margin: 0;
- font-size: 16px;
- font-weight: 600;
- }
- .loading {
- color: #1890ff;
- font-size: 12px;
- }
- .recommendation-info {
- margin-bottom: 16px;
- padding: 12px;
- background: #e6f7ff;
- border: 1px solid #91d5ff;
- border-radius: 6px;
- font-size: 13px;
- }
- .recommendation-info .reason {
- margin-bottom: 8px;
- color: #0050b3;
- }
- .warnings {
- margin-top: 8px;
- padding-top: 8px;
- border-top: 1px dashed #91d5ff;
- }
- .warning-item {
- color: #d4380d;
- margin: 4px 0;
- }
- .mode-options {
- display: grid;
- grid-template-columns: 1fr 1fr;
- gap: 12px;
- }
- .mode-option {
- display: flex;
- gap: 8px;
- padding: 12px;
- border: 2px solid #d9d9d9;
- border-radius: 8px;
- cursor: pointer;
- transition: all 0.2s;
- background: white;
- }
- .mode-option:hover {
- border-color: #1890ff;
- background: #f0f7ff;
- }
- .mode-option.selected {
- border-color: #1890ff;
- background: #e6f7ff;
- box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
- }
- .mode-option input[type="radio"] {
- margin-top: 4px;
- cursor: pointer;
- }
- .mode-content {
- flex: 1;
- }
- .mode-title {
- font-weight: 600;
- font-size: 14px;
- margin-bottom: 4px;
- }
- .mode-desc {
- font-size: 12px;
- color: #666;
- margin-bottom: 8px;
- line-height: 1.4;
- }
- .mode-benefits {
- font-size: 11px;
- line-height: 1.6;
- color: #555;
- }
- @media (max-width: 768px) {
- .mode-options {
- grid-template-columns: 1fr;
- }
- }
- `}</style>
- </div>
- );
- };
|