Files
aurak/web/components/ModeSelector.tsx
T
Developer 0b0a060967 fix: 全部TS错误修复(25->0) + 证书API 500修复 + i18n缺失key补全 + 类型定义修正
- 证书API 500修复: AssessmentCertificate实体注册到app.module.ts
- 前端TS错误25个清零: i18n key 17个, 类型定义8个
- i18n补全: 17个缺失key添加到zh/en/ja
- KnowledgeFile类型: 添加title, content字段
- importService: 改用apiClient.request替代raw fetch
- ModeSelector: 移除jsx prop
- questionBankService: .ok -> .status >= 400
- NotebookDetailView: .filter -> .items.filter
- ImportTasksDrawer: tasks.items提取
- API端点审计: 16/16通过
- 数据库Schema审计: 25表288列一致
- AGENTS.md更新
2026-05-18 08:30:59 +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>{`
.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>
);
};