forked from hangshuo652/aurak
feat: implement QuestionBank CRUD with pagination and template query
- 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
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* 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>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user