first-update
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { deleteQuestion } from '@/lib/db/questions';
|
||||
|
||||
// 删除单个问题
|
||||
export async function DELETE(request, { params }) {
|
||||
try {
|
||||
const { projectId, questionId } = params;
|
||||
|
||||
// 验证参数
|
||||
if (!projectId) {
|
||||
return NextResponse.json({ error: 'Project ID is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
if (!questionId) {
|
||||
return NextResponse.json({ error: 'Question ID is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
// 删除问题
|
||||
await deleteQuestion(questionId);
|
||||
|
||||
return NextResponse.json({ success: true, message: 'Delete successful' });
|
||||
} catch (error) {
|
||||
console.error('Delete failed:', String(error));
|
||||
return NextResponse.json({ error: error.message || 'Delete failed' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { batchDeleteQuestions } from '@/lib/db/questions';
|
||||
|
||||
// 批量删除问题
|
||||
export async function DELETE(request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { questionIds } = body;
|
||||
|
||||
// 验证参数
|
||||
if (questionIds.length === 0) {
|
||||
return NextResponse.json({ error: 'Question ID is required' }, { status: 400 });
|
||||
}
|
||||
|
||||
// 删除问题
|
||||
await batchDeleteQuestions(questionIds);
|
||||
|
||||
return NextResponse.json({ success: true, message: 'Delete successful' });
|
||||
} catch (error) {
|
||||
console.error('Delete failed:', String(error));
|
||||
return NextResponse.json({ error: error.message || 'Delete failed' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export async function POST(request, { params }) {
|
||||
try {
|
||||
const { projectId } = params;
|
||||
const body = await request.json();
|
||||
const { format, selectedIds, filters } = body;
|
||||
|
||||
let questions;
|
||||
|
||||
// 如果有选中的问题 ID,按 ID 获取
|
||||
if (selectedIds && selectedIds.length > 0) {
|
||||
questions = await getQuestionsByIds(projectId, selectedIds);
|
||||
} else {
|
||||
// 否则获取全部问题(不限分页)
|
||||
questions = await getAllQuestions(
|
||||
projectId,
|
||||
filters?.searchTerm || '',
|
||||
filters?.chunkName || '',
|
||||
filters?.sourceType || 'all'
|
||||
);
|
||||
}
|
||||
|
||||
// 固定导出字段:问题内容、文本块名称、问题标签
|
||||
const filteredQuestions = questions.map(q => ({
|
||||
question: q.question,
|
||||
chunkName: q.chunk?.name || q.chunkName || '',
|
||||
questionLabel: q.questionLabel || ''
|
||||
}));
|
||||
|
||||
return NextResponse.json(filteredQuestions);
|
||||
} catch (error) {
|
||||
console.error('Failed to export questions:', error);
|
||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// 获取全部问题(不限分页)
|
||||
async function getAllQuestions(projectId, searchTerm = '', chunkName = '', sourceType = 'all') {
|
||||
const { db } = await import('@/lib/db/index');
|
||||
|
||||
const whereClause = {
|
||||
projectId
|
||||
};
|
||||
|
||||
// 搜索条件
|
||||
if (searchTerm) {
|
||||
whereClause.OR = [{ question: { contains: searchTerm } }, { questionLabel: { contains: searchTerm } }];
|
||||
}
|
||||
|
||||
// 文本块名称筛选
|
||||
if (chunkName) {
|
||||
whereClause.chunk = {
|
||||
name: { contains: chunkName }
|
||||
};
|
||||
}
|
||||
|
||||
// 数据源类型筛选
|
||||
if (sourceType === 'text') {
|
||||
whereClause.imageName = null;
|
||||
} else if (sourceType === 'image') {
|
||||
whereClause.imageName = { not: null };
|
||||
}
|
||||
|
||||
return await db.questions.findMany({
|
||||
where: whereClause,
|
||||
include: {
|
||||
chunk: {
|
||||
select: {
|
||||
name: true
|
||||
}
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
createAt: 'desc'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 根据 ID 列表获取问题
|
||||
async function getQuestionsByIds(projectId, questionIds) {
|
||||
const { db } = await import('@/lib/db/index');
|
||||
|
||||
return await db.questions.findMany({
|
||||
where: {
|
||||
projectId,
|
||||
id: { in: questionIds }
|
||||
},
|
||||
include: {
|
||||
chunk: {
|
||||
select: {
|
||||
name: true
|
||||
}
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
createAt: 'desc'
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import {
|
||||
getAllQuestionsByProjectId,
|
||||
getQuestions,
|
||||
getQuestionsIds,
|
||||
saveQuestions,
|
||||
updateQuestion
|
||||
} from '@/lib/db/questions';
|
||||
import { getImageById, getImageChunk } from '@/lib/db/images';
|
||||
|
||||
// 获取项目的所有问题
|
||||
export async function GET(request, { params }) {
|
||||
try {
|
||||
const { projectId } = params;
|
||||
// 验证项目ID
|
||||
if (!projectId) {
|
||||
return NextResponse.json({ error: 'Missing project ID' }, { status: 400 });
|
||||
}
|
||||
const { searchParams } = new URL(request.url);
|
||||
let status = searchParams.get('status');
|
||||
let answered = undefined;
|
||||
if (status === 'answered') answered = true;
|
||||
if (status === 'unanswered') answered = false;
|
||||
const chunkName = searchParams.get('chunkName');
|
||||
const sourceType = searchParams.get('sourceType') || 'all'; // 'all', 'text', 'image'
|
||||
const searchMatchMode = searchParams.get('searchMatchMode') || 'match'; // 'match', 'notMatch'
|
||||
let selectedAll = searchParams.get('selectedAll');
|
||||
if (selectedAll) {
|
||||
let data = await getQuestionsIds(
|
||||
projectId,
|
||||
answered,
|
||||
searchParams.get('input'),
|
||||
chunkName,
|
||||
sourceType,
|
||||
searchMatchMode
|
||||
);
|
||||
return NextResponse.json(data);
|
||||
}
|
||||
let all = searchParams.get('all');
|
||||
if (all) {
|
||||
let data = await getAllQuestionsByProjectId(projectId);
|
||||
return NextResponse.json(data);
|
||||
}
|
||||
// 获取问题列表
|
||||
const questions = await getQuestions(
|
||||
projectId,
|
||||
parseInt(searchParams.get('page')),
|
||||
parseInt(searchParams.get('size')),
|
||||
answered,
|
||||
searchParams.get('input'),
|
||||
chunkName,
|
||||
sourceType,
|
||||
searchMatchMode
|
||||
);
|
||||
|
||||
return NextResponse.json(questions);
|
||||
} catch (error) {
|
||||
console.error('Failed to get questions:', String(error));
|
||||
return NextResponse.json({ error: error.message || 'Failed to get questions' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// 新增问题
|
||||
export async function POST(request, { params }) {
|
||||
try {
|
||||
const { projectId } = params;
|
||||
const body = await request.json();
|
||||
const { question, chunkId, label } = body;
|
||||
|
||||
// 验证必要参数
|
||||
if (!projectId || !question) {
|
||||
return NextResponse.json({ error: 'Missing necessary parameters' }, { status: 400 });
|
||||
}
|
||||
|
||||
if (!body.chunkId && body.imageId) {
|
||||
const chunk = await getImageChunk(projectId);
|
||||
body.chunkId = chunk.id;
|
||||
body.label = 'image';
|
||||
}
|
||||
|
||||
// 添加新问题
|
||||
let questions = [body];
|
||||
// 保存更新后的数据
|
||||
let data = await saveQuestions(projectId, questions);
|
||||
|
||||
// 返回成功响应
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
console.error('Failed to create question:', String(error));
|
||||
return NextResponse.json({ error: error.message || 'Failed to create question' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// 更新问题
|
||||
export async function PUT(request) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
// 保存更新后的数据
|
||||
const { imageId } = body;
|
||||
if (imageId) {
|
||||
body.imageName = (await getImageById(imageId))?.imageName;
|
||||
}
|
||||
let data = await updateQuestion(body);
|
||||
// 返回更新后的问题数据
|
||||
return NextResponse.json(data);
|
||||
} catch (error) {
|
||||
console.error('更新问题失败:', String(error));
|
||||
return NextResponse.json({ error: error.message || '更新问题失败' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import templateDb from '@/lib/db/questionTemplates';
|
||||
import { generateQuestionsFromTemplateEdit } from '@/lib/services/questions/template';
|
||||
|
||||
// 获取单个模板
|
||||
export async function GET(request, { params }) {
|
||||
try {
|
||||
const { templateId } = params;
|
||||
|
||||
const template = await templateDb.getTemplateById(templateId);
|
||||
|
||||
if (!template) {
|
||||
return NextResponse.json({ error: '模板不存在' }, { status: 404 });
|
||||
}
|
||||
|
||||
// 获取使用统计
|
||||
const usageCount = await templateDb.getTemplateUsageCount(templateId);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
template: {
|
||||
...template,
|
||||
usageCount
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to get template:', error);
|
||||
return NextResponse.json({ error: error.message || 'Failed to get template' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// 更新问题模板
|
||||
export async function PUT(request, { params }) {
|
||||
try {
|
||||
const { projectId, templateId } = params;
|
||||
const data = await request.json();
|
||||
|
||||
const { question, sourceType, answerType, description, labels, customFormat, order, autoGenerate } = data;
|
||||
|
||||
// 验证数据源类型
|
||||
if (sourceType && !['image', 'text'].includes(sourceType)) {
|
||||
return NextResponse.json({ error: '无效的数据源类型' }, { status: 400 });
|
||||
}
|
||||
|
||||
// 验证答案类型
|
||||
if (answerType && !['text', 'label', 'custom_format'].includes(answerType)) {
|
||||
return NextResponse.json({ error: '无效的答案类型' }, { status: 400 });
|
||||
}
|
||||
|
||||
const updateData = {};
|
||||
if (question !== undefined) updateData.question = question;
|
||||
if (sourceType !== undefined) updateData.sourceType = sourceType;
|
||||
if (answerType !== undefined) updateData.answerType = answerType;
|
||||
if (description !== undefined) updateData.description = description;
|
||||
if (labels !== undefined) updateData.labels = labels;
|
||||
if (customFormat !== undefined) updateData.customFormat = customFormat;
|
||||
if (order !== undefined) updateData.order = order;
|
||||
|
||||
const template = await templateDb.updateTemplate(templateId, updateData);
|
||||
|
||||
let generationResult = null;
|
||||
|
||||
// 如果启用自动生成,则为还未创建此模板问题的数据源创建问题
|
||||
if (autoGenerate) {
|
||||
try {
|
||||
generationResult = await generateQuestionsFromTemplateEdit(projectId, template);
|
||||
} catch (error) {
|
||||
console.error('编辑模式自动生成问题失败:', error);
|
||||
generationResult = {
|
||||
success: false,
|
||||
successCount: 0,
|
||||
failCount: 0,
|
||||
message: '自动生成问题时发生错误'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
template,
|
||||
generation: generationResult
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to update template:', error);
|
||||
return NextResponse.json({ error: error.message || 'Failed to update template' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// 删除问题模板
|
||||
export async function DELETE(request, { params }) {
|
||||
try {
|
||||
const { templateId } = params;
|
||||
|
||||
// 检查是否有关联的问题
|
||||
const usageCount = await templateDb.getTemplateUsageCount(templateId);
|
||||
if (usageCount > 0) {
|
||||
return NextResponse.json({ error: `此模板已被 ${usageCount} 个问题使用,无法删除` }, { status: 400 });
|
||||
}
|
||||
|
||||
await templateDb.deleteTemplate(templateId);
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
message: '模板删除成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to delete template:', error);
|
||||
return NextResponse.json({ error: error.message || 'Failed to delete template' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import templateDb from '@/lib/db/questionTemplates';
|
||||
import { generateQuestionsFromTemplate, checkTemplateGenerationAvailability } from '@/lib/services/questions/template';
|
||||
|
||||
// 获取问题模板列表
|
||||
export async function GET(request, { params }) {
|
||||
try {
|
||||
const { projectId } = params;
|
||||
const { searchParams } = new URL(request.url);
|
||||
const sourceType = searchParams.get('sourceType');
|
||||
const search = searchParams.get('search');
|
||||
|
||||
const templates = await templateDb.getTemplates(projectId, { sourceType, search });
|
||||
|
||||
// 获取使用统计
|
||||
const templateIds = templates.map(t => t.id);
|
||||
const usageCounts = await templateDb.getTemplatesUsageCount(templateIds);
|
||||
|
||||
// 添加使用统计到模板数据
|
||||
const templatesWithUsage = templates.map(template => ({
|
||||
...template,
|
||||
usageCount: usageCounts[template.id] || 0
|
||||
}));
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
templates: templatesWithUsage
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to get templates:', error);
|
||||
return NextResponse.json({ error: error.message || 'Failed to get templates' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// 创建问题模板
|
||||
export async function POST(request, { params }) {
|
||||
try {
|
||||
const { projectId } = params;
|
||||
const data = await request.json();
|
||||
|
||||
const { question, sourceType, answerType, description, labels, customFormat, order, autoGenerate } = data;
|
||||
|
||||
// 验证必填字段
|
||||
if (!question || !sourceType || !answerType) {
|
||||
return NextResponse.json({ error: '缺少必要参数:question, sourceType, answerType' }, { status: 400 });
|
||||
}
|
||||
|
||||
// 验证数据源类型
|
||||
if (!['image', 'text'].includes(sourceType)) {
|
||||
return NextResponse.json({ error: '无效的数据源类型' }, { status: 400 });
|
||||
}
|
||||
|
||||
// 验证答案类型
|
||||
if (!['text', 'label', 'custom_format'].includes(answerType)) {
|
||||
return NextResponse.json({ error: '无效的答案类型' }, { status: 400 });
|
||||
}
|
||||
|
||||
// 如果是标签类型,验证 labels
|
||||
if (answerType === 'label' && (!labels || !Array.isArray(labels) || labels.length === 0)) {
|
||||
return NextResponse.json({ error: '标签类型问题必须提供标签列表' }, { status: 400 });
|
||||
}
|
||||
|
||||
// 如果是自定义格式,验证 customFormat
|
||||
if (answerType === 'custom_format' && !customFormat) {
|
||||
return NextResponse.json({ error: '自定义格式问题必须提供格式定义' }, { status: 400 });
|
||||
}
|
||||
|
||||
const template = await templateDb.createTemplate(projectId, {
|
||||
question,
|
||||
sourceType,
|
||||
answerType,
|
||||
description,
|
||||
labels: answerType === 'label' ? labels : [],
|
||||
customFormat: answerType === 'custom_format' ? customFormat : null,
|
||||
order: order || 0
|
||||
});
|
||||
|
||||
let generationResult = null;
|
||||
|
||||
// 如果启用自动生成,则为所有相关数据源创建问题
|
||||
if (autoGenerate) {
|
||||
try {
|
||||
// 先检查是否有可用的数据源
|
||||
const availability = await checkTemplateGenerationAvailability(projectId, sourceType);
|
||||
|
||||
if (availability.available) {
|
||||
generationResult = await generateQuestionsFromTemplate(projectId, template);
|
||||
} else {
|
||||
generationResult = {
|
||||
success: false,
|
||||
successCount: 0,
|
||||
failCount: 0,
|
||||
message: availability.message
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('自动生成问题失败:', error);
|
||||
generationResult = {
|
||||
success: false,
|
||||
successCount: 0,
|
||||
failCount: 0,
|
||||
message: '自动生成问题时发生错误'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
template,
|
||||
generation: generationResult
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Failed to create template:', error);
|
||||
return NextResponse.json({ error: error.message || 'Failed to create template' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getQuestionsForTree, getQuestionsByTag } from '@/lib/db/questions';
|
||||
|
||||
/**
|
||||
* 获取项目的问题树形视图数据
|
||||
* @param {Request} request - 请求对象
|
||||
* @param {Object} params - 路由参数
|
||||
* @returns {Promise<Response>} - 包含问题数据的响应
|
||||
*/
|
||||
export async function GET(request, { params }) {
|
||||
try {
|
||||
const { projectId } = params;
|
||||
|
||||
// 验证项目ID
|
||||
if (!projectId) {
|
||||
return NextResponse.json({ error: '项目ID不能为空' }, { status: 400 });
|
||||
}
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const tag = searchParams.get('tag');
|
||||
const input = searchParams.get('input');
|
||||
const tagsOnly = searchParams.get('tagsOnly') === 'true';
|
||||
const isDistill = searchParams.get('isDistill') === 'true';
|
||||
// 默认排除图片问题(label='image'),可通过 excludeImage=false 参数改变
|
||||
const excludeImage = searchParams.get('excludeImage') !== 'false';
|
||||
|
||||
if (tag) {
|
||||
// 获取指定标签的问题数据(包含完整字段)
|
||||
const questions = await getQuestionsByTag(projectId, tag, input, isDistill, excludeImage);
|
||||
return NextResponse.json(questions);
|
||||
} else if (tagsOnly) {
|
||||
// 只获取标签信息(仅包含 id 和 label 字段)
|
||||
const treeData = await getQuestionsForTree(projectId, input, isDistill, excludeImage);
|
||||
return NextResponse.json(treeData);
|
||||
} else {
|
||||
// 兼容原有请求,获取树形视图数据(仅包含 id 和 label 字段)
|
||||
const treeData = await getQuestionsForTree(projectId, null, isDistill, excludeImage);
|
||||
return NextResponse.json(treeData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取问题树形数据失败:', String(error));
|
||||
return NextResponse.json({ error: error.message || '获取问题树形数据失败' }, { status: 500 });
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user