first-update
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import axios from 'axios';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
export function useQuestionDelete(projectId, onDeleteSuccess) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// 确认对话框状态
|
||||
const [confirmDialog, setConfirmDialog] = useState({
|
||||
open: false,
|
||||
title: '',
|
||||
content: '',
|
||||
confirmAction: null
|
||||
});
|
||||
|
||||
// 执行单个问题删除
|
||||
const executeDeleteQuestion = async (questionId, selectedQuestions, setSelectedQuestions) => {
|
||||
toast.promise(axios.delete(`/api/projects/${projectId}/questions/${questionId}`), {
|
||||
loading: '数据删除中',
|
||||
success: data => {
|
||||
// 更新选中状态
|
||||
setSelectedQuestions(prev => (prev.includes(questionId) ? prev.filter(id => id !== questionId) : prev));
|
||||
// 调用成功回调
|
||||
if (onDeleteSuccess) {
|
||||
onDeleteSuccess();
|
||||
}
|
||||
return t('common.deleteSuccess');
|
||||
},
|
||||
error: error => {
|
||||
return error.response?.data?.message || '删除失败';
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 确认删除单个问题
|
||||
const confirmDeleteQuestion = (questionId, selectedQuestions, setSelectedQuestions) => {
|
||||
setConfirmDialog({
|
||||
open: true,
|
||||
title: t('common.confirmDelete'),
|
||||
content: t('common.confirmDeleteQuestion'),
|
||||
confirmAction: () => executeDeleteQuestion(questionId, selectedQuestions, setSelectedQuestions)
|
||||
});
|
||||
};
|
||||
|
||||
// 处理删除单个问题的入口函数
|
||||
const handleDeleteQuestion = (questionId, selectedQuestions, setSelectedQuestions) => {
|
||||
confirmDeleteQuestion(questionId, selectedQuestions, setSelectedQuestions);
|
||||
};
|
||||
|
||||
// 执行批量删除问题
|
||||
const executeBatchDeleteQuestions = async (selectedQuestions, setSelectedQuestions) => {
|
||||
toast.promise(
|
||||
axios.delete(`/api/projects/${projectId}/questions/batch-delete`, {
|
||||
data: { questionIds: selectedQuestions }
|
||||
}),
|
||||
{
|
||||
loading: `正在删除 ${selectedQuestions.length} 个问题...`,
|
||||
success: data => {
|
||||
// 调用成功回调
|
||||
if (onDeleteSuccess) {
|
||||
onDeleteSuccess();
|
||||
}
|
||||
// 清空选中状态
|
||||
setSelectedQuestions([]);
|
||||
return `成功删除 ${selectedQuestions.length} 个问题`;
|
||||
},
|
||||
error: error => {
|
||||
return error.response?.data?.message || '批量删除问题失败';
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// 确认批量删除问题
|
||||
const confirmBatchDeleteQuestions = (selectedQuestions, setSelectedQuestions) => {
|
||||
if (selectedQuestions.length === 0) {
|
||||
toast.warning('请先选择问题');
|
||||
return;
|
||||
}
|
||||
|
||||
setConfirmDialog({
|
||||
open: true,
|
||||
title: '确认批量删除问题',
|
||||
content: `您确定要删除选中的 ${selectedQuestions.length} 个问题吗?此操作不可恢复。`,
|
||||
confirmAction: () => executeBatchDeleteQuestions(selectedQuestions, setSelectedQuestions)
|
||||
});
|
||||
};
|
||||
|
||||
// 处理批量删除问题的入口函数
|
||||
const handleBatchDeleteQuestions = (selectedQuestions, setSelectedQuestions) => {
|
||||
confirmBatchDeleteQuestions(selectedQuestions, setSelectedQuestions);
|
||||
};
|
||||
|
||||
// 关闭确认对话框
|
||||
const closeConfirmDialog = () => {
|
||||
setConfirmDialog({
|
||||
open: false,
|
||||
title: '',
|
||||
content: '',
|
||||
confirmAction: null
|
||||
});
|
||||
};
|
||||
|
||||
// 确认对话框的确认操作
|
||||
const handleConfirmAction = () => {
|
||||
closeConfirmDialog();
|
||||
if (confirmDialog.confirmAction) {
|
||||
confirmDialog.confirmAction();
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
// 状态
|
||||
confirmDialog,
|
||||
|
||||
// 方法
|
||||
handleDeleteQuestion,
|
||||
handleBatchDeleteQuestions,
|
||||
closeConfirmDialog,
|
||||
handleConfirmAction
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import request from '@/lib/util/request';
|
||||
|
||||
export function useQuestionEdit(projectId, onSuccess) {
|
||||
const { t } = useTranslation();
|
||||
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
||||
const [editMode, setEditMode] = useState('create');
|
||||
const [editingQuestion, setEditingQuestion] = useState(null);
|
||||
|
||||
const handleOpenCreateDialog = () => {
|
||||
setEditMode('create');
|
||||
setEditingQuestion(null);
|
||||
setEditDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleOpenEditDialog = question => {
|
||||
setEditMode('edit');
|
||||
setEditingQuestion(question);
|
||||
setEditDialogOpen(true);
|
||||
};
|
||||
|
||||
const handleCloseDialog = () => {
|
||||
setEditDialogOpen(false);
|
||||
setEditingQuestion(null);
|
||||
};
|
||||
|
||||
const handleSubmitQuestion = async formData => {
|
||||
try {
|
||||
const response = await request(`/api/projects/${projectId}/questions`, {
|
||||
method: editMode === 'create' ? 'POST' : 'PUT',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(
|
||||
editMode === 'create'
|
||||
? {
|
||||
question: formData.question,
|
||||
chunkId: formData.chunkId,
|
||||
label: formData.label,
|
||||
imageId: formData.imageId,
|
||||
imageName: formData.imageName
|
||||
}
|
||||
: {
|
||||
id: formData.id,
|
||||
question: formData.question,
|
||||
chunkId: formData.chunkId,
|
||||
label: formData.label,
|
||||
imageId: formData.imageId,
|
||||
imageName: formData.imageName
|
||||
}
|
||||
)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error || t('questions.operationFailed'));
|
||||
}
|
||||
|
||||
// 获取更新后的问题数据
|
||||
const updatedQuestion = await response.json();
|
||||
|
||||
// 直接更新问题列表中的数据,而不是重新获取整个列表
|
||||
if (onSuccess) {
|
||||
onSuccess(updatedQuestion);
|
||||
}
|
||||
handleCloseDialog();
|
||||
} catch (error) {
|
||||
console.error('操作失败:', error);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
editDialogOpen,
|
||||
editMode,
|
||||
editingQuestion,
|
||||
handleOpenCreateDialog,
|
||||
handleOpenEditDialog,
|
||||
handleCloseDialog,
|
||||
handleSubmitQuestion
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,114 @@
|
||||
'use client';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toast } from 'sonner';
|
||||
import axios from 'axios';
|
||||
|
||||
const useQuestionExport = projectId => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// 导出问题集
|
||||
const exportQuestions = async exportOptions => {
|
||||
try {
|
||||
const apiUrl = `/api/projects/${projectId}/questions/export`;
|
||||
const requestBody = {
|
||||
format: exportOptions.format || 'json'
|
||||
};
|
||||
|
||||
// 如果有选中的问题 ID,传递 ID 列表
|
||||
if (exportOptions.selectedIds && exportOptions.selectedIds.length > 0) {
|
||||
requestBody.selectedIds = exportOptions.selectedIds;
|
||||
}
|
||||
|
||||
// 如果有筛选条件,传递筛选参数
|
||||
if (exportOptions.filters) {
|
||||
requestBody.filters = exportOptions.filters;
|
||||
}
|
||||
|
||||
const response = await axios.post(apiUrl, requestBody);
|
||||
const questions = response.data;
|
||||
|
||||
// 处理和下载数据
|
||||
await processAndDownloadData(questions, exportOptions);
|
||||
|
||||
toast.success(t('questions.exportSuccess'));
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Export failed:', error);
|
||||
toast.error(error.message || t('questions.exportFailed'));
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// 处理和下载数据的通用函数
|
||||
const processAndDownloadData = async (data, exportOptions) => {
|
||||
const format = exportOptions.format || 'json';
|
||||
let content;
|
||||
let filename;
|
||||
let mimeType;
|
||||
|
||||
const timestamp = new Date().toISOString().split('T')[0];
|
||||
|
||||
switch (format) {
|
||||
case 'json':
|
||||
content = JSON.stringify(data, null, 2);
|
||||
filename = `questions-${projectId}-${timestamp}.json`;
|
||||
mimeType = 'application/json';
|
||||
break;
|
||||
|
||||
case 'jsonl':
|
||||
content = data.map(item => JSON.stringify(item)).join('\n');
|
||||
filename = `questions-${projectId}-${timestamp}.jsonl`;
|
||||
mimeType = 'application/jsonl';
|
||||
break;
|
||||
|
||||
case 'txt':
|
||||
content = data.map(item => item.question).join('\n\n');
|
||||
filename = `questions-${projectId}-${timestamp}.txt`;
|
||||
mimeType = 'text/plain';
|
||||
break;
|
||||
|
||||
case 'csv':
|
||||
// CSV 格式
|
||||
const headers = Object.keys(data[0] || {});
|
||||
const csvRows = [headers.join(',')];
|
||||
data.forEach(item => {
|
||||
const values = headers.map(header => {
|
||||
const value = item[header] || '';
|
||||
// 处理包含逗号或引号的值
|
||||
if (typeof value === 'string' && (value.includes(',') || value.includes('"') || value.includes('\n'))) {
|
||||
return `"${value.replace(/"/g, '""')}"`;
|
||||
}
|
||||
return value;
|
||||
});
|
||||
csvRows.push(values.join(','));
|
||||
});
|
||||
content = csvRows.join('\n');
|
||||
filename = `questions-${projectId}-${timestamp}.csv`;
|
||||
mimeType = 'text/csv';
|
||||
break;
|
||||
|
||||
default:
|
||||
content = JSON.stringify(data, null, 2);
|
||||
filename = `questions-${projectId}-${timestamp}.json`;
|
||||
mimeType = 'application/json';
|
||||
}
|
||||
|
||||
// 创建下载链接
|
||||
const blob = new Blob([content], { type: mimeType });
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
return {
|
||||
exportQuestions
|
||||
};
|
||||
};
|
||||
|
||||
export default useQuestionExport;
|
||||
@@ -0,0 +1,291 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toast } from 'sonner';
|
||||
import axios from 'axios';
|
||||
import i18n from '@/lib/i18n';
|
||||
import request from '@/lib/util/request';
|
||||
import { processInParallel } from '@/lib/util/processInParallel';
|
||||
|
||||
export function useQuestionGeneration(projectId, model, taskSettings, getQuestionList) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
// 处理状态
|
||||
const [processing, setProcessing] = useState(false);
|
||||
|
||||
// 进度状态
|
||||
const [progress, setProgress] = useState({
|
||||
total: 0, // 总共选择的问题数量
|
||||
completed: 0, // 已处理完成的数量
|
||||
percentage: 0, // 进度百分比
|
||||
datasetCount: 0 // 已生成的数据集数量
|
||||
});
|
||||
|
||||
// 批量生成答案
|
||||
const handleBatchGenerateAnswers = async selectedQuestions => {
|
||||
if (selectedQuestions.length === 0) {
|
||||
toast.warning(t('questions.noQuestionsSelected'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!model) {
|
||||
toast.warning(t('models.configNotFound'));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setProgress({
|
||||
total: selectedQuestions.length,
|
||||
completed: 0,
|
||||
percentage: 0,
|
||||
datasetCount: 0
|
||||
});
|
||||
|
||||
// 然后设置处理状态为真,确保进度条显示
|
||||
setProcessing(true);
|
||||
|
||||
toast.info(t('questions.batchGenerateStart', { count: selectedQuestions.length }));
|
||||
|
||||
// 单个问题处理函数
|
||||
const processQuestion = async questionId => {
|
||||
try {
|
||||
console.log('开始生成数据集:', { questionId });
|
||||
const language = i18n.language === 'zh-CN' ? '中文' : 'en';
|
||||
// 调用API生成数据集
|
||||
const response = await request(`/api/projects/${projectId}/datasets`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
questionId,
|
||||
model,
|
||||
language
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json();
|
||||
console.error(t('datasets.generateError'), errorData.error || t('datasets.generateFailed'));
|
||||
|
||||
// 更新进度状态(即使失败也计入已处理)
|
||||
setProgress(prev => {
|
||||
const completed = prev.completed + 1;
|
||||
const percentage = Math.round((completed / prev.total) * 100);
|
||||
|
||||
return {
|
||||
...prev,
|
||||
completed,
|
||||
percentage
|
||||
};
|
||||
});
|
||||
|
||||
return { success: false, questionId, error: errorData.error || t('datasets.generateFailed') };
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
// 更新进度状态
|
||||
setProgress(prev => {
|
||||
const completed = prev.completed + 1;
|
||||
const percentage = Math.round((completed / prev.total) * 100);
|
||||
const datasetCount = prev.datasetCount + 1;
|
||||
|
||||
return {
|
||||
...prev,
|
||||
completed,
|
||||
percentage,
|
||||
datasetCount
|
||||
};
|
||||
});
|
||||
|
||||
console.log(`数据集生成成功: ${questionId}`);
|
||||
return { success: true, questionId, data: data.dataset };
|
||||
} catch (error) {
|
||||
console.error('生成数据集失败:', error);
|
||||
|
||||
// 更新进度状态(即使失败也计入已处理)
|
||||
setProgress(prev => {
|
||||
const completed = prev.completed + 1;
|
||||
const percentage = Math.round((completed / prev.total) * 100);
|
||||
|
||||
return {
|
||||
...prev,
|
||||
completed,
|
||||
percentage
|
||||
};
|
||||
});
|
||||
|
||||
return { success: false, questionId, error: error.message };
|
||||
}
|
||||
};
|
||||
|
||||
// 并行处理所有问题,最多同时处理2个
|
||||
const results = await processInParallel(selectedQuestions, processQuestion, taskSettings.concurrencyLimit);
|
||||
|
||||
// 刷新数据
|
||||
getQuestionList();
|
||||
|
||||
// 处理完成后设置结果消息
|
||||
const successCount = results.filter(r => r.success).length;
|
||||
const failCount = results.filter(r => !r.success).length;
|
||||
|
||||
if (failCount > 0) {
|
||||
toast.warning(
|
||||
t('datasets.partialSuccess', {
|
||||
successCount,
|
||||
total: selectedQuestions.length,
|
||||
failCount
|
||||
})
|
||||
);
|
||||
} else {
|
||||
toast.success(t('common.success', { successCount }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('生成数据集出错:', error);
|
||||
toast.error(error.message || '生成数据集失败');
|
||||
} finally {
|
||||
// 延迟关闭处理状态,确保用户可以看到完成的进度
|
||||
setTimeout(() => {
|
||||
setProcessing(false);
|
||||
// 再次延迟重置进度状态
|
||||
setTimeout(() => {
|
||||
setProgress({
|
||||
total: 0,
|
||||
completed: 0,
|
||||
percentage: 0,
|
||||
datasetCount: 0
|
||||
});
|
||||
}, 500);
|
||||
}, 2000); // 延迟关闭处理状态,让用户看到完成的进度
|
||||
}
|
||||
};
|
||||
|
||||
// 自动生成数据集
|
||||
const handleAutoGenerateDatasets = async () => {
|
||||
try {
|
||||
if (!model) {
|
||||
toast.error(t('questions.selectModelFirst', { defaultValue: '请先选择模型' }));
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用创建任务接口
|
||||
const response = await axios.post(`/api/projects/${projectId}/tasks`, {
|
||||
taskType: 'answer-generation',
|
||||
modelInfo: model,
|
||||
language: i18n.language
|
||||
});
|
||||
|
||||
if (response.data?.code === 0) {
|
||||
toast.success(t('tasks.createSuccess', { defaultValue: '后台任务已创建,系统将自动处理未生成答案的问题' }));
|
||||
} else {
|
||||
toast.error(t('tasks.createFailed', { defaultValue: '创建后台任务失败' }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建任务失败:', error);
|
||||
toast.error(t('tasks.createFailed', { defaultValue: '创建任务失败' }) + ': ' + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
// 自动生成图像问答数据集
|
||||
const handleAutoGenerateImageDatasets = async () => {
|
||||
try {
|
||||
if (!model) {
|
||||
toast.error(t('questions.selectModelFirst', { defaultValue: '请先选择模型' }));
|
||||
return;
|
||||
}
|
||||
|
||||
if (model.type !== 'vision') {
|
||||
toast.error(t('images.visionModelRequired', { defaultValue: '请选择支持视觉的模型' }));
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用创建任务接口
|
||||
const response = await axios.post(`/api/projects/${projectId}/tasks`, {
|
||||
taskType: 'image-dataset-generation',
|
||||
modelInfo: model,
|
||||
language: i18n.language
|
||||
});
|
||||
|
||||
if (response.data?.code === 0) {
|
||||
toast.success(t('tasks.createSuccess', { defaultValue: '后台任务已创建,系统将自动处理未生成答案的图片问题' }));
|
||||
} else {
|
||||
toast.error(t('tasks.createFailed', { defaultValue: '创建后台任务失败' }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建图片数据集任务失败:', error);
|
||||
toast.error(t('tasks.createFailed', { defaultValue: '创建任务失败' }) + ': ' + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
// 自动生成多轮对话数据集
|
||||
const handleAutoGenerateMultiTurnDatasets = async () => {
|
||||
try {
|
||||
if (!model) {
|
||||
toast.error(t('questions.selectModelFirst', { defaultValue: '请先选择模型' }));
|
||||
return;
|
||||
}
|
||||
|
||||
// 首先检查项目是否配置了多轮对话设置
|
||||
const configResponse = await axios.get(`/api/projects/${projectId}/tasks`);
|
||||
if (configResponse.status !== 200) {
|
||||
throw new Error('获取项目配置失败');
|
||||
}
|
||||
|
||||
const config = configResponse.data;
|
||||
const multiTurnConfig = {
|
||||
systemPrompt: config.multiTurnSystemPrompt,
|
||||
scenario: config.multiTurnScenario,
|
||||
rounds: config.multiTurnRounds,
|
||||
roleA: config.multiTurnRoleA,
|
||||
roleB: config.multiTurnRoleB
|
||||
};
|
||||
|
||||
// 检查是否已配置必要的多轮对话设置
|
||||
if (
|
||||
!multiTurnConfig.scenario ||
|
||||
!multiTurnConfig.roleA ||
|
||||
!multiTurnConfig.roleB ||
|
||||
!multiTurnConfig.rounds ||
|
||||
multiTurnConfig.rounds < 1
|
||||
) {
|
||||
toast.error(t('questions.multiTurnNotConfigured', '请先在项目设置中配置多轮对话相关参数'));
|
||||
return;
|
||||
}
|
||||
|
||||
// 调用创建任务接口
|
||||
const response = await axios.post(`/api/projects/${projectId}/tasks`, {
|
||||
taskType: 'multi-turn-generation',
|
||||
modelInfo: model,
|
||||
language: i18n.language,
|
||||
config: JSON.stringify(multiTurnConfig)
|
||||
});
|
||||
|
||||
if (response.data?.code === 0) {
|
||||
toast.success(
|
||||
t('tasks.multiTurnCreateSuccess', {
|
||||
defaultValue: '多轮对话生成任务已创建,系统将自动处理未生成多轮对话的问题'
|
||||
})
|
||||
);
|
||||
} else {
|
||||
toast.error(t('tasks.createFailed', { defaultValue: '创建后台任务失败' }));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('创建多轮对话任务失败:', error);
|
||||
toast.error(t('tasks.createFailed', { defaultValue: '创建任务失败' }) + ': ' + error.message);
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
// 状态
|
||||
processing,
|
||||
progress,
|
||||
|
||||
// 方法
|
||||
handleBatchGenerateAnswers,
|
||||
handleAutoGenerateDatasets,
|
||||
handleAutoGenerateMultiTurnDatasets,
|
||||
handleAutoGenerateImageDatasets
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import axios from 'axios';
|
||||
import { toast } from 'sonner';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
/**
|
||||
* 问题模板管理 Hook
|
||||
* @param {string} projectId - 项目ID
|
||||
* @param {string} sourceType - 数据源类型: 'image' | 'text' | null (null表示获取所有)
|
||||
*/
|
||||
export function useQuestionTemplates(projectId, sourceType = null) {
|
||||
const { t } = useTranslation();
|
||||
const [templates, setTemplates] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
// 获取模板列表
|
||||
const fetchTemplates = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const params = sourceType ? `?sourceType=${sourceType}` : '';
|
||||
const response = await axios.get(`/api/projects/${projectId}/questions/templates${params}`);
|
||||
if (response.data.success) {
|
||||
setTemplates(response.data.templates);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch templates:', error);
|
||||
toast.error(t('questions.fetchTemplatesFailed'));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// 创建模板
|
||||
const createTemplate = async data => {
|
||||
try {
|
||||
const response = await axios.post(`/api/projects/${projectId}/questions/templates`, data);
|
||||
if (response.data.success) {
|
||||
const { template, generation } = response.data;
|
||||
|
||||
// 显示模板创建成功消息
|
||||
toast.success(t('questions.createTemplateSuccess'));
|
||||
|
||||
// 如果有自动生成结果,显示相应消息
|
||||
if (generation) {
|
||||
if (generation.success) {
|
||||
if (generation.successCount > 0) {
|
||||
toast.success(
|
||||
t('questions.template.autoGenerateSuccess', {
|
||||
count: generation.successCount
|
||||
})
|
||||
);
|
||||
}
|
||||
if (generation.failCount > 0) {
|
||||
toast.warning(
|
||||
t('questions.template.autoGeneratePartialFail', {
|
||||
success: generation.successCount,
|
||||
fail: generation.failCount
|
||||
})
|
||||
);
|
||||
}
|
||||
} else {
|
||||
toast.error(generation.message || t('questions.template.autoGenerateFailed'));
|
||||
}
|
||||
}
|
||||
|
||||
fetchTemplates();
|
||||
return template;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to create template:', error);
|
||||
toast.error(t('questions.createTemplateFailed'));
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// 更新模板
|
||||
const updateTemplate = async (templateId, data) => {
|
||||
try {
|
||||
const response = await axios.put(`/api/projects/${projectId}/questions/templates/${templateId}`, data);
|
||||
if (response.data.success) {
|
||||
toast.success(t('questions.updateTemplateSuccess'));
|
||||
fetchTemplates();
|
||||
return response.data.template;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to update template:', error);
|
||||
toast.error(t('questions.updateTemplateFailed'));
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// 删除模板
|
||||
const deleteTemplate = async templateId => {
|
||||
try {
|
||||
const response = await axios.delete(`/api/projects/${projectId}/questions/templates/${templateId}`);
|
||||
if (response.data.success) {
|
||||
toast.success(t('questions.deleteTemplateSuccess'));
|
||||
fetchTemplates();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to delete template:', error);
|
||||
toast.error(t('questions.deleteTemplateFailed'));
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
// 初始加载
|
||||
useEffect(() => {
|
||||
if (projectId) {
|
||||
fetchTemplates();
|
||||
}
|
||||
}, [projectId, sourceType]);
|
||||
|
||||
return {
|
||||
templates,
|
||||
loading,
|
||||
createTemplate,
|
||||
updateTemplate,
|
||||
deleteTemplate,
|
||||
refetch: fetchTemplates
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useDebounce } from '@/hooks/useDebounce';
|
||||
import axios from 'axios';
|
||||
|
||||
export function useQuestionsFilter(projectId) {
|
||||
// 过滤和搜索状态
|
||||
const [answerFilter, setAnswerFilter] = useState('all'); // 'all', 'answered', 'unanswered'
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [searchMatchMode, setSearchMatchMode] = useState('match'); // 'match', 'notMatch'
|
||||
const [chunkNameFilter, setChunkNameFilter] = useState('');
|
||||
const [sourceTypeFilter, setSourceTypeFilter] = useState('all'); // 'all', 'text', 'image'
|
||||
const debouncedSearchTerm = useDebounce(searchTerm);
|
||||
const debouncedChunkNameFilter = useDebounce(chunkNameFilter);
|
||||
|
||||
// 选择状态
|
||||
const [selectedQuestions, setSelectedQuestions] = useState([]);
|
||||
|
||||
// 处理问题选择
|
||||
const handleSelectQuestion = (questionKey, newSelected) => {
|
||||
if (newSelected) {
|
||||
// 处理批量选择的情况
|
||||
setSelectedQuestions(newSelected);
|
||||
} else {
|
||||
// 处理单个问题选择的情况
|
||||
setSelectedQuestions(prev => {
|
||||
if (prev.includes(questionKey)) {
|
||||
return prev.filter(id => id !== questionKey);
|
||||
} else {
|
||||
return [...prev, questionKey];
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 全选/取消全选
|
||||
const handleSelectAll = async () => {
|
||||
if (selectedQuestions.length > 0) {
|
||||
setSelectedQuestions([]);
|
||||
} else {
|
||||
const response = await axios.get(
|
||||
`/api/projects/${projectId}/questions?status=${answerFilter}&input=${searchTerm}&searchMatchMode=${searchMatchMode}&chunkName=${encodeURIComponent(chunkNameFilter)}&sourceType=${sourceTypeFilter}&selectedAll=1`
|
||||
);
|
||||
setSelectedQuestions(response.data.map(dataset => dataset.id));
|
||||
}
|
||||
};
|
||||
|
||||
// 处理搜索输入变化
|
||||
const handleSearchChange = event => {
|
||||
setSearchTerm(event.target.value);
|
||||
};
|
||||
|
||||
// 处理过滤器变化
|
||||
const handleFilterChange = event => {
|
||||
setAnswerFilter(event.target.value);
|
||||
};
|
||||
|
||||
// 处理文本块名称筛选变化
|
||||
const handleChunkNameFilterChange = event => {
|
||||
setChunkNameFilter(event.target.value);
|
||||
};
|
||||
|
||||
// 处理数据源类型筛选变化
|
||||
const handleSourceTypeFilterChange = event => {
|
||||
setSourceTypeFilter(event.target.value);
|
||||
};
|
||||
|
||||
// 处理搜索匹配模式变化
|
||||
const handleSearchMatchModeChange = event => {
|
||||
setSearchMatchMode(event.target.value);
|
||||
};
|
||||
|
||||
// 清空选择
|
||||
const clearSelection = () => {
|
||||
setSelectedQuestions([]);
|
||||
};
|
||||
|
||||
// 重置所有过滤条件
|
||||
const resetFilters = () => {
|
||||
setSearchTerm('');
|
||||
setSearchMatchMode('match');
|
||||
setAnswerFilter('all');
|
||||
setChunkNameFilter('');
|
||||
setSourceTypeFilter('all');
|
||||
setSelectedQuestions([]);
|
||||
};
|
||||
|
||||
return {
|
||||
// 状态
|
||||
answerFilter,
|
||||
searchTerm,
|
||||
debouncedSearchTerm,
|
||||
searchMatchMode,
|
||||
chunkNameFilter,
|
||||
debouncedChunkNameFilter,
|
||||
sourceTypeFilter,
|
||||
selectedQuestions,
|
||||
|
||||
// 方法
|
||||
setAnswerFilter,
|
||||
setSearchTerm,
|
||||
setSearchMatchMode,
|
||||
setChunkNameFilter,
|
||||
setSourceTypeFilter,
|
||||
setSelectedQuestions,
|
||||
handleSelectQuestion,
|
||||
handleSelectAll,
|
||||
handleSearchChange,
|
||||
handleFilterChange,
|
||||
handleChunkNameFilterChange,
|
||||
handleSourceTypeFilterChange,
|
||||
handleSearchMatchModeChange,
|
||||
clearSelection,
|
||||
resetFilters
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user