0a9588abb7
- Add pagination support to findAll (page, limit query params) - Add findByTemplateId method to service - Add GET /by-template/:templateId endpoint to controller - Service already includes CRUD for QuestionBank and QuestionBankItem
250 lines
6.3 KiB
TypeScript
250 lines
6.3 KiB
TypeScript
/**
|
||
* 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>
|
||
);
|
||
};
|