246 lines
6.7 KiB
JavaScript
246 lines
6.7 KiB
JavaScript
|
|
// HuggingFaceTab.js 组件
|
|||
|
|
import React, { useState, useEffect } from 'react';
|
|||
|
|
import { useTranslation } from 'react-i18next';
|
|||
|
|
import {
|
|||
|
|
Typography,
|
|||
|
|
Box,
|
|||
|
|
TextField,
|
|||
|
|
Button,
|
|||
|
|
FormControlLabel,
|
|||
|
|
Checkbox,
|
|||
|
|
Alert,
|
|||
|
|
CircularProgress,
|
|||
|
|
Divider,
|
|||
|
|
Paper,
|
|||
|
|
Grid,
|
|||
|
|
Tooltip,
|
|||
|
|
IconButton,
|
|||
|
|
Link
|
|||
|
|
} from '@mui/material';
|
|||
|
|
import InfoIcon from '@mui/icons-material/Info';
|
|||
|
|
import HelpOutlineIcon from '@mui/icons-material/HelpOutline';
|
|||
|
|
import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
|
|||
|
|
|
|||
|
|
const HuggingFaceTab = ({
|
|||
|
|
projectId,
|
|||
|
|
systemPrompt,
|
|||
|
|
reasoningLanguage,
|
|||
|
|
confirmedOnly,
|
|||
|
|
includeCOT,
|
|||
|
|
formatType,
|
|||
|
|
fileFormat,
|
|||
|
|
customFields,
|
|||
|
|
handleSystemPromptChange,
|
|||
|
|
handleReasoningLanguageChange,
|
|||
|
|
handleConfirmedOnlyChange,
|
|||
|
|
handleIncludeCOTChange
|
|||
|
|
}) => {
|
|||
|
|
const { t } = useTranslation();
|
|||
|
|
const [token, setToken] = useState('');
|
|||
|
|
const [datasetName, setDatasetName] = useState('');
|
|||
|
|
const [isPrivate, setIsPrivate] = useState(false);
|
|||
|
|
const [uploading, setUploading] = useState(false);
|
|||
|
|
const [error, setError] = useState('');
|
|||
|
|
const [success, setSuccess] = useState(false);
|
|||
|
|
const [datasetUrl, setDatasetUrl] = useState('');
|
|||
|
|
const [hasToken, setHasToken] = useState(false);
|
|||
|
|
const [loading, setLoading] = useState(true);
|
|||
|
|
|
|||
|
|
// 从配置中获取 huggingfaceToken
|
|||
|
|
useEffect(() => {
|
|||
|
|
if (projectId) {
|
|||
|
|
setLoading(true);
|
|||
|
|
fetch(`/api/projects/${projectId}/config`)
|
|||
|
|
.then(res => res.json())
|
|||
|
|
.then(data => {
|
|||
|
|
if (data.huggingfaceToken) {
|
|||
|
|
setToken(data.huggingfaceToken);
|
|||
|
|
setHasToken(true);
|
|||
|
|
}
|
|||
|
|
setLoading(false);
|
|||
|
|
})
|
|||
|
|
.catch(err => {
|
|||
|
|
console.error('获取 HuggingFace Token 失败:', err);
|
|||
|
|
setLoading(false);
|
|||
|
|
});
|
|||
|
|
}
|
|||
|
|
}, [projectId]);
|
|||
|
|
|
|||
|
|
// 处理上传数据集到 HuggingFace
|
|||
|
|
const handleUpload = async () => {
|
|||
|
|
if (!hasToken) {
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if (!datasetName) {
|
|||
|
|
setError('请输入数据集名称');
|
|||
|
|
return;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
setUploading(true);
|
|||
|
|
setError('');
|
|||
|
|
setSuccess(false);
|
|||
|
|
|
|||
|
|
const response = await fetch(`/api/projects/${projectId}/huggingface/upload`, {
|
|||
|
|
method: 'POST',
|
|||
|
|
headers: {
|
|||
|
|
'Content-Type': 'application/json'
|
|||
|
|
},
|
|||
|
|
body: JSON.stringify({
|
|||
|
|
token,
|
|||
|
|
datasetName,
|
|||
|
|
isPrivate,
|
|||
|
|
formatType,
|
|||
|
|
systemPrompt,
|
|||
|
|
reasoningLanguage,
|
|||
|
|
confirmedOnly,
|
|||
|
|
includeCOT,
|
|||
|
|
fileFormat,
|
|||
|
|
customFields: formatType === 'custom' ? customFields : undefined
|
|||
|
|
})
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
const data = await response.json();
|
|||
|
|
if (!response.ok) {
|
|||
|
|
throw new Error(data.error || '上传失败');
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
setSuccess(true);
|
|||
|
|
setDatasetUrl(data.url);
|
|||
|
|
} catch (err) {
|
|||
|
|
setError(err.message);
|
|||
|
|
} finally {
|
|||
|
|
setUploading(false);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<Box sx={{ mt: 2 }}>
|
|||
|
|
{error && (
|
|||
|
|
<Alert severity="error" sx={{ mb: 2 }}>
|
|||
|
|
{error}
|
|||
|
|
</Alert>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{success && (
|
|||
|
|
<Alert severity="success" sx={{ mb: 2 }} icon={<CheckCircleOutlineIcon fontSize="inherit" />}>
|
|||
|
|
{t('export.uploadSuccess')}
|
|||
|
|
{datasetUrl && (
|
|||
|
|
<Box mt={1}>
|
|||
|
|
<Link href={datasetUrl} target="_blank" rel="noopener noreferrer">
|
|||
|
|
{t('export.viewOnHuggingFace')}
|
|||
|
|
</Link>
|
|||
|
|
</Box>
|
|||
|
|
)}
|
|||
|
|
</Alert>
|
|||
|
|
)}
|
|||
|
|
|
|||
|
|
{!hasToken ? (
|
|||
|
|
<Alert severity="warning" sx={{ mb: 3 }}>
|
|||
|
|
{t('export.noTokenWarning')}
|
|||
|
|
<Box mt={1}>
|
|||
|
|
<Button
|
|||
|
|
variant="outlined"
|
|||
|
|
size="small"
|
|||
|
|
onClick={() => (window.location.href = `/projects/${projectId}/settings`)}
|
|||
|
|
>
|
|||
|
|
{t('export.goToSettings')}
|
|||
|
|
</Button>
|
|||
|
|
</Box>
|
|||
|
|
</Alert>
|
|||
|
|
) : null}
|
|||
|
|
|
|||
|
|
<Divider sx={{ my: 2 }} />
|
|||
|
|
|
|||
|
|
<Box sx={{ mb: 3 }}>
|
|||
|
|
<Typography variant="subtitle1" fontWeight="bold" gutterBottom>
|
|||
|
|
{t('export.datasetSettings')}
|
|||
|
|
</Typography>
|
|||
|
|
|
|||
|
|
<Grid container spacing={2}>
|
|||
|
|
<Grid item xs={12}>
|
|||
|
|
<TextField
|
|||
|
|
fullWidth
|
|||
|
|
label={t('export.datasetName')}
|
|||
|
|
placeholder="username/dataset-name"
|
|||
|
|
value={datasetName}
|
|||
|
|
onChange={e => setDatasetName(e.target.value)}
|
|||
|
|
helperText={t('export.datasetNameHelp')}
|
|||
|
|
sx={{ mb: 2 }}
|
|||
|
|
/>
|
|||
|
|
</Grid>
|
|||
|
|
|
|||
|
|
<Grid item xs={12}>
|
|||
|
|
<FormControlLabel
|
|||
|
|
control={<Checkbox checked={isPrivate} onChange={e => setIsPrivate(e.target.checked)} />}
|
|||
|
|
label={t('export.privateDataset')}
|
|||
|
|
/>
|
|||
|
|
</Grid>
|
|||
|
|
</Grid>
|
|||
|
|
</Box>
|
|||
|
|
|
|||
|
|
<Box sx={{ mb: 3 }}>
|
|||
|
|
<Typography variant="subtitle1" fontWeight="bold" gutterBottom>
|
|||
|
|
{t('export.exportOptions')}
|
|||
|
|
</Typography>
|
|||
|
|
|
|||
|
|
<Box sx={{ mb: 2 }}>
|
|||
|
|
<Typography variant="subtitle2" gutterBottom>
|
|||
|
|
{t('export.systemPrompt')}
|
|||
|
|
</Typography>
|
|||
|
|
<TextField
|
|||
|
|
fullWidth
|
|||
|
|
multiline
|
|||
|
|
rows={3}
|
|||
|
|
value={systemPrompt}
|
|||
|
|
onChange={handleSystemPromptChange}
|
|||
|
|
variant="outlined"
|
|||
|
|
/>
|
|||
|
|
</Box>
|
|||
|
|
{/* Reasoning language – only for multilingual‑thinking */}
|
|||
|
|
{formatType === 'multilingualthinking' && (
|
|||
|
|
<Box sx={{ mb: 3 }}>
|
|||
|
|
<Typography variant="subtitle1" fontWeight="bold" gutterBottom>
|
|||
|
|
{t('export.reasoningLanguage')}
|
|||
|
|
</Typography>
|
|||
|
|
<TextField
|
|||
|
|
fullWidth
|
|||
|
|
rows={3}
|
|||
|
|
multiline
|
|||
|
|
variant="outlined"
|
|||
|
|
placeholder={t('export.reasoningLanguage')}
|
|||
|
|
value={reasoningLanguage}
|
|||
|
|
onChange={handleReasoningLanguageChange}
|
|||
|
|
/>
|
|||
|
|
</Box>
|
|||
|
|
)}
|
|||
|
|
<Box sx={{ display: 'flex', flexDirection: 'row', gap: 4 }}>
|
|||
|
|
<FormControlLabel
|
|||
|
|
control={<Checkbox checked={confirmedOnly} onChange={handleConfirmedOnlyChange} />}
|
|||
|
|
label={t('export.onlyConfirmed')}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
<FormControlLabel
|
|||
|
|
control={<Checkbox checked={includeCOT} onChange={handleIncludeCOTChange} />}
|
|||
|
|
label={t('export.includeCOT')}
|
|||
|
|
/>
|
|||
|
|
</Box>
|
|||
|
|
</Box>
|
|||
|
|
|
|||
|
|
<Box sx={{ display: 'flex', justifyContent: 'flex-end', mt: 3 }}>
|
|||
|
|
<Button
|
|||
|
|
variant="contained"
|
|||
|
|
onClick={handleUpload}
|
|||
|
|
disabled={uploading || !hasToken || !datasetName}
|
|||
|
|
sx={{ borderRadius: 2 }}
|
|||
|
|
>
|
|||
|
|
{uploading ? <CircularProgress size={24} /> : t('export.uploadToHuggingFace')}
|
|||
|
|
</Button>
|
|||
|
|
</Box>
|
|||
|
|
</Box>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
export default HuggingFaceTab;
|