first-update

This commit is contained in:
2026-03-17 14:36:31 +08:00
parent 72f08aee7c
commit 4eddf05e79
516 changed files with 115270 additions and 1 deletions

View File

@@ -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 });
}
}

View File

@@ -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 });
}
}

View File

@@ -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'
}
});
}

View File

@@ -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 });
}
}

View File

@@ -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 });
}
}

View File

@@ -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 });
}
}

View File

@@ -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 });
}
}