239 lines
7.8 KiB
JavaScript
239 lines
7.8 KiB
JavaScript
'use client';
|
||
|
||
import {
|
||
Dialog,
|
||
DialogTitle,
|
||
DialogContent,
|
||
DialogActions,
|
||
Button,
|
||
TextField,
|
||
Box,
|
||
Typography,
|
||
Select,
|
||
MenuItem,
|
||
FormControl,
|
||
InputLabel,
|
||
Slider,
|
||
Card,
|
||
CardContent,
|
||
IconButton,
|
||
CircularProgress
|
||
} from '@mui/material';
|
||
import DeleteIcon from '@mui/icons-material/Delete';
|
||
import { useState, useEffect } from 'react';
|
||
import { useTranslation } from 'react-i18next';
|
||
|
||
/**
|
||
* 评估集变体编辑对话框
|
||
*/
|
||
export default function EvalVariantDialog({ open, onClose, onGenerate, onSave }) {
|
||
const { t } = useTranslation();
|
||
const [step, setStep] = useState('config'); // 'config' | 'preview'
|
||
const [loading, setLoading] = useState(false);
|
||
const [config, setConfig] = useState({
|
||
questionType: 'open_ended',
|
||
count: 1
|
||
});
|
||
const [items, setItems] = useState([]);
|
||
|
||
// Reset state when dialog opens
|
||
useEffect(() => {
|
||
if (open) {
|
||
setStep('config');
|
||
setConfig({ questionType: 'open_ended', count: 1 });
|
||
setItems([]);
|
||
setLoading(false);
|
||
}
|
||
}, [open]);
|
||
|
||
const handleGenerate = async () => {
|
||
setLoading(true);
|
||
try {
|
||
const data = await onGenerate(config);
|
||
// Ensure data is an array
|
||
const newItems = Array.isArray(data) ? data : [data];
|
||
setItems(newItems);
|
||
setStep('preview');
|
||
} catch (error) {
|
||
console.error(error);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
const handleSave = () => {
|
||
onSave(items);
|
||
};
|
||
|
||
const handleItemChange = (index, field, value) => {
|
||
const newItems = [...items];
|
||
newItems[index] = { ...newItems[index], [field]: value };
|
||
setItems(newItems);
|
||
};
|
||
|
||
const handleDeleteItem = index => {
|
||
const newItems = items.filter((_, i) => i !== index);
|
||
setItems(newItems);
|
||
if (newItems.length === 0) {
|
||
setStep('config');
|
||
}
|
||
};
|
||
|
||
const renderConfigStep = () => (
|
||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 3, mt: 1 }}>
|
||
<Typography variant="body2" color="text.secondary">
|
||
{t('datasets.evalVariantConfigHint', '请选择生成的题目类型和数量,AI 将基于当前问答对进行改写。')}
|
||
</Typography>
|
||
|
||
<FormControl fullWidth>
|
||
<InputLabel>{t('datasets.questionType', '题目类型')}</InputLabel>
|
||
<Select
|
||
value={config.questionType}
|
||
label={t('datasets.questionType', '题目类型')}
|
||
onChange={e => setConfig({ ...config, questionType: e.target.value })}
|
||
>
|
||
<MenuItem value="open_ended">{t('datasets.typeOpenEnded', '开放式问答')}</MenuItem>
|
||
<MenuItem value="single_choice">{t('datasets.typeSingleChoice', '单选题')}</MenuItem>
|
||
<MenuItem value="multiple_choice">{t('datasets.typeMultipleChoice', '多选题')}</MenuItem>
|
||
<MenuItem value="true_false">{t('datasets.typeTrueFalse', '判断题')}</MenuItem>
|
||
<MenuItem value="short_answer">{t('datasets.typeShortAnswer', '简答题')}</MenuItem>
|
||
</Select>
|
||
</FormControl>
|
||
|
||
<Box>
|
||
<Typography gutterBottom>
|
||
{t('datasets.generateCount', '生成数量')}: {config.count}
|
||
</Typography>
|
||
<Slider
|
||
value={config.count}
|
||
onChange={(_, value) => setConfig({ ...config, count: value })}
|
||
step={1}
|
||
marks
|
||
min={1}
|
||
max={5}
|
||
valueLabelDisplay="auto"
|
||
/>
|
||
</Box>
|
||
</Box>
|
||
);
|
||
|
||
const renderPreviewStep = () => (
|
||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, mt: 1 }}>
|
||
<Typography variant="body2" color="text.secondary">
|
||
{t('datasets.evalVariantPreviewHint', '您可以编辑生成的题目,确认无误后保存到评估集。')}
|
||
</Typography>
|
||
|
||
{items.map((item, index) => (
|
||
<Card key={index} variant="outlined">
|
||
<CardContent sx={{ position: 'relative', display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||
<IconButton
|
||
size="small"
|
||
onClick={() => handleDeleteItem(index)}
|
||
sx={{ position: 'absolute', right: 8, top: 8 }}
|
||
>
|
||
<DeleteIcon fontSize="small" />
|
||
</IconButton>
|
||
|
||
<Typography variant="subtitle2" color="primary">
|
||
{t('datasets.questionIndex', '题目 {{index}}', { index: index + 1 })}
|
||
</Typography>
|
||
|
||
<TextField
|
||
label={t('datasets.question', '问题')}
|
||
fullWidth
|
||
multiline
|
||
rows={2}
|
||
value={item.question || ''}
|
||
onChange={e => handleItemChange(index, 'question', e.target.value)}
|
||
size="small"
|
||
/>
|
||
|
||
{/* Render Options for choice questions */}
|
||
{(item.options || config.questionType.includes('choice')) && (
|
||
<TextField
|
||
label={t('datasets.options', '选项 (JSON数组)')}
|
||
fullWidth
|
||
multiline
|
||
rows={2}
|
||
value={Array.isArray(item.options) ? JSON.stringify(item.options) : item.options || ''}
|
||
onChange={e => {
|
||
let val = e.target.value;
|
||
try {
|
||
// Try to parse if user inputs valid JSON, otherwise keep string
|
||
const parsed = JSON.parse(val);
|
||
if (Array.isArray(parsed)) val = parsed;
|
||
} catch (e) {}
|
||
handleItemChange(index, 'options', val);
|
||
}}
|
||
helperText={t('datasets.optionsHint', '例如: ["选项A", "选项B"]')}
|
||
size="small"
|
||
/>
|
||
)}
|
||
|
||
<TextField
|
||
label={t('datasets.answer', '答案')}
|
||
fullWidth
|
||
multiline
|
||
rows={2}
|
||
value={Array.isArray(item.correctAnswer) ? JSON.stringify(item.correctAnswer) : item.correctAnswer || ''}
|
||
onChange={e => {
|
||
let val = e.target.value;
|
||
// For multiple choice, answer might be array
|
||
if (config.questionType === 'multiple_choice') {
|
||
try {
|
||
const parsed = JSON.parse(val);
|
||
if (Array.isArray(parsed)) val = parsed;
|
||
} catch (e) {}
|
||
}
|
||
handleItemChange(index, 'correctAnswer', val);
|
||
}}
|
||
helperText={
|
||
config.questionType === 'multiple_choice'
|
||
? t('datasets.answerArrayHint', '多选题答案请输入数组,如 ["A", "C"]')
|
||
: config.questionType === 'true_false'
|
||
? t('datasets.answerBoolHint', '判断题答案请输入 ✅ 或 ❌')
|
||
: ''
|
||
}
|
||
size="small"
|
||
/>
|
||
</CardContent>
|
||
</Card>
|
||
))}
|
||
</Box>
|
||
);
|
||
|
||
return (
|
||
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
|
||
<DialogTitle>
|
||
{step === 'config'
|
||
? t('datasets.evalVariantTitle', '生成评估集变体')
|
||
: t('datasets.evalVariantPreviewTitle', '确认生成的题目')}
|
||
</DialogTitle>
|
||
|
||
<DialogContent dividers>{step === 'config' ? renderConfigStep() : renderPreviewStep()}</DialogContent>
|
||
|
||
<DialogActions>
|
||
<Button onClick={onClose} disabled={loading}>
|
||
{t('common.cancel')}
|
||
</Button>
|
||
|
||
{step === 'config' ? (
|
||
<Button
|
||
onClick={handleGenerate}
|
||
variant="contained"
|
||
color="primary"
|
||
disabled={loading}
|
||
startIcon={loading && <CircularProgress size={20} color="inherit" />}
|
||
>
|
||
{loading ? t('common.generating', '生成中...') : t('datasets.generate', '生成')}
|
||
</Button>
|
||
) : (
|
||
<Button onClick={handleSave} variant="contained" color="primary" disabled={items.length === 0}>
|
||
{t('datasets.saveToEval', '保存到评估集')}
|
||
</Button>
|
||
)}
|
||
</DialogActions>
|
||
</Dialog>
|
||
);
|
||
}
|