190 lines
4.8 KiB
JavaScript
190 lines
4.8 KiB
JavaScript
|
|
'use client';
|
||
|
|
|
||
|
|
import { useState, useCallback, useEffect } from 'react';
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 评估数据集导出 Hook
|
||
|
|
* 管理导出对话框状态、筛选条件和导出逻辑
|
||
|
|
*/
|
||
|
|
export default function useExportEvalDatasets(projectId, stats = {}) {
|
||
|
|
// 对话框状态
|
||
|
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||
|
|
const [exporting, setExporting] = useState(false);
|
||
|
|
const [error, setError] = useState('');
|
||
|
|
|
||
|
|
// 导出配置
|
||
|
|
const [format, setFormat] = useState('json');
|
||
|
|
const [questionTypes, setQuestionTypes] = useState([]);
|
||
|
|
const [selectedTags, setSelectedTags] = useState([]);
|
||
|
|
const [keyword, setKeyword] = useState('');
|
||
|
|
|
||
|
|
// 预览数据
|
||
|
|
const [previewTotal, setPreviewTotal] = useState(0);
|
||
|
|
const [previewLoading, setPreviewLoading] = useState(false);
|
||
|
|
|
||
|
|
// 从 stats 中获取可用的标签列表
|
||
|
|
const availableTags = stats?.byTag ? Object.keys(stats.byTag).sort() : [];
|
||
|
|
|
||
|
|
// 当筛选条件变化时,获取预览数量
|
||
|
|
useEffect(() => {
|
||
|
|
if (!dialogOpen || !projectId) return;
|
||
|
|
|
||
|
|
const controller = new AbortController();
|
||
|
|
|
||
|
|
const fetchPreview = async () => {
|
||
|
|
try {
|
||
|
|
setPreviewLoading(true);
|
||
|
|
const params = new URLSearchParams();
|
||
|
|
|
||
|
|
if (questionTypes.length > 0) {
|
||
|
|
questionTypes.forEach(t => params.append('questionTypes', t));
|
||
|
|
}
|
||
|
|
if (selectedTags.length > 0) {
|
||
|
|
selectedTags.forEach(t => params.append('tags', t));
|
||
|
|
}
|
||
|
|
if (keyword.trim()) {
|
||
|
|
params.append('keyword', keyword.trim());
|
||
|
|
}
|
||
|
|
|
||
|
|
const response = await fetch(`/api/projects/${projectId}/eval-datasets/export?${params.toString()}`, {
|
||
|
|
signal: controller.signal
|
||
|
|
});
|
||
|
|
|
||
|
|
if (response.ok) {
|
||
|
|
const result = await response.json();
|
||
|
|
setPreviewTotal(result?.data?.total ?? 0);
|
||
|
|
}
|
||
|
|
} catch (err) {
|
||
|
|
if (err.name !== 'AbortError') {
|
||
|
|
console.error('获取导出预览失败:', err);
|
||
|
|
}
|
||
|
|
} finally {
|
||
|
|
setPreviewLoading(false);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
fetchPreview();
|
||
|
|
|
||
|
|
return () => {
|
||
|
|
controller.abort();
|
||
|
|
};
|
||
|
|
}, [dialogOpen, projectId, questionTypes, selectedTags, keyword]);
|
||
|
|
|
||
|
|
// 打开对话框
|
||
|
|
const openDialog = useCallback(() => {
|
||
|
|
setDialogOpen(true);
|
||
|
|
setError('');
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
// 关闭对话框
|
||
|
|
const closeDialog = useCallback(() => {
|
||
|
|
if (exporting) return;
|
||
|
|
setDialogOpen(false);
|
||
|
|
// 重置状态
|
||
|
|
setFormat('json');
|
||
|
|
setQuestionTypes([]);
|
||
|
|
setSelectedTags([]);
|
||
|
|
setKeyword('');
|
||
|
|
setError('');
|
||
|
|
}, [exporting]);
|
||
|
|
|
||
|
|
// 重置筛选条件
|
||
|
|
const resetFilters = useCallback(() => {
|
||
|
|
setQuestionTypes([]);
|
||
|
|
setSelectedTags([]);
|
||
|
|
setKeyword('');
|
||
|
|
}, []);
|
||
|
|
|
||
|
|
// 执行导出
|
||
|
|
const handleExport = useCallback(async () => {
|
||
|
|
if (previewTotal === 0) {
|
||
|
|
setError('没有符合条件的数据可导出');
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
try {
|
||
|
|
setExporting(true);
|
||
|
|
setError('');
|
||
|
|
|
||
|
|
const response = await fetch(`/api/projects/${projectId}/eval-datasets/export`, {
|
||
|
|
method: 'POST',
|
||
|
|
headers: { 'Content-Type': 'application/json' },
|
||
|
|
body: JSON.stringify({
|
||
|
|
format,
|
||
|
|
questionTypes,
|
||
|
|
tags: selectedTags,
|
||
|
|
keyword: keyword.trim()
|
||
|
|
})
|
||
|
|
});
|
||
|
|
|
||
|
|
if (!response.ok) {
|
||
|
|
const result = await response.json();
|
||
|
|
throw new Error(result.error || '导出失败');
|
||
|
|
}
|
||
|
|
|
||
|
|
// 获取文件名
|
||
|
|
const contentDisposition = response.headers.get('Content-Disposition');
|
||
|
|
let filename = `eval-datasets-${Date.now()}.${format}`;
|
||
|
|
if (contentDisposition) {
|
||
|
|
const match = contentDisposition.match(/filename="?([^"]+)"?/);
|
||
|
|
if (match) {
|
||
|
|
filename = match[1];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// 下载文件
|
||
|
|
const blob = await response.blob();
|
||
|
|
const url = window.URL.createObjectURL(blob);
|
||
|
|
const a = document.createElement('a');
|
||
|
|
a.href = url;
|
||
|
|
a.download = filename;
|
||
|
|
document.body.appendChild(a);
|
||
|
|
a.click();
|
||
|
|
document.body.removeChild(a);
|
||
|
|
window.URL.revokeObjectURL(url);
|
||
|
|
|
||
|
|
// 导出成功,关闭对话框
|
||
|
|
closeDialog();
|
||
|
|
|
||
|
|
return true;
|
||
|
|
} catch (err) {
|
||
|
|
console.error('导出失败:', err);
|
||
|
|
setError(err.message || '导出失败');
|
||
|
|
return false;
|
||
|
|
} finally {
|
||
|
|
setExporting(false);
|
||
|
|
}
|
||
|
|
}, [projectId, format, questionTypes, selectedTags, keyword, previewTotal, closeDialog]);
|
||
|
|
|
||
|
|
return {
|
||
|
|
// 对话框状态
|
||
|
|
dialogOpen,
|
||
|
|
openDialog,
|
||
|
|
closeDialog,
|
||
|
|
|
||
|
|
// 导出状态
|
||
|
|
exporting,
|
||
|
|
error,
|
||
|
|
setError,
|
||
|
|
|
||
|
|
// 导出配置
|
||
|
|
format,
|
||
|
|
setFormat,
|
||
|
|
questionTypes,
|
||
|
|
setQuestionTypes,
|
||
|
|
selectedTags,
|
||
|
|
setSelectedTags,
|
||
|
|
keyword,
|
||
|
|
setKeyword,
|
||
|
|
|
||
|
|
// 预览数据
|
||
|
|
previewTotal,
|
||
|
|
previewLoading,
|
||
|
|
availableTags,
|
||
|
|
|
||
|
|
// 操作
|
||
|
|
resetFilters,
|
||
|
|
handleExport
|
||
|
|
};
|
||
|
|
}
|