Files
aurak/web/components/ModeSelector.tsx
T
Developer 0a9588abb7 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
2026-04-23 17:19:11 +08:00

250 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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>
);
};