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,161 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db/index';
import LLMClient from '@/lib/llm/core/index';
import { getModelConfigById } from '@/lib/db/model-config';
/**
* Get current question and generate answers from two models
*/
export async function GET(request, { params }) {
try {
const { projectId, taskId } = params;
const task = await db.task.findFirst({
where: {
id: taskId,
projectId,
taskType: 'blind-test'
}
});
if (!task) {
return NextResponse.json({ code: 404, error: 'Task not found' }, { status: 404 });
}
if (task.status !== 0) {
return NextResponse.json({ code: 400, error: 'Task has ended' }, { status: 400 });
}
// Parse task detail
let detail = {};
let modelInfo = {};
try {
detail = task.detail ? JSON.parse(task.detail) : {};
modelInfo = task.modelInfo ? JSON.parse(task.modelInfo) : {};
} catch (e) {
console.error('Failed to parse task detail:', e);
}
const questionIds = detail.questionIds || detail.evalDatasetIds || [];
const currentIndex = detail.currentIndex || 0;
// Check if all questions are completed
if (questionIds.length === 0 || currentIndex >= questionIds.length) {
return NextResponse.json({
code: 0,
data: {
completed: true,
message: 'All questions completed'
}
});
}
// Fetch current question
const currentQuestionId = questionIds[currentIndex];
const currentQuestion = await db.evalDatasets.findUnique({
where: { id: currentQuestionId },
select: {
id: true,
question: true,
questionType: true,
correctAnswer: true,
tags: true
}
});
if (!currentQuestion) {
return NextResponse.json({ code: 404, error: 'Question not found' }, { status: 404 });
}
// Fetch both model configs
const [modelConfigA, modelConfigB] = await Promise.all([
getModelConfigById(modelInfo.modelA.providerId),
getModelConfigById(modelInfo.modelB.providerId)
]);
if (!modelConfigA || !modelConfigB) {
return NextResponse.json({ code: 400, error: 'Model configuration not found' }, { status: 400 });
}
// Build prompts
const systemPrompt = "You are a helpful assistant. Provide detailed and accurate answers to the user's question.";
const userPrompt = currentQuestion.question;
// Call both models in parallel
const startTimeA = Date.now();
const startTimeB = Date.now();
let answerA = '';
let answerB = '';
let errorA = null;
let errorB = null;
let durationA = 0;
let durationB = 0;
try {
// Call model A
const clientA = new LLMClient(modelConfigA);
const resultA = await clientA.chat([
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt }
]);
answerA = resultA.text || '';
durationA = Date.now() - startTimeA;
} catch (err) {
console.error('Model A call failed:', err);
errorA = err.message;
durationA = Date.now() - startTimeA;
}
try {
// Call model B
const clientB = new LLMClient(modelConfigB);
const resultB = await clientB.chat([
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt }
]);
answerB = resultB.text || '';
durationB = Date.now() - startTimeB;
} catch (err) {
console.error('Model B call failed:', err);
errorB = err.message;
durationB = Date.now() - startTimeB;
}
// Randomly swap positions (core blind-test behavior)
const isSwapped = Math.random() > 0.5;
return NextResponse.json({
code: 0,
data: {
completed: false,
currentIndex,
totalCount: evalDatasetIds.length,
question: currentQuestion,
// Blind test: do not reveal which model is which
leftAnswer: {
content: isSwapped ? answerB : answerA,
error: isSwapped ? errorB : errorA,
duration: isSwapped ? durationB : durationA
},
rightAnswer: {
content: isSwapped ? answerA : answerB,
error: isSwapped ? errorA : errorB,
duration: isSwapped ? durationA : durationB
},
// Server stores the actual mapping for scoring
_swap: isSwapped
}
});
} catch (error) {
console.error('Failed to fetch current question:', error);
return NextResponse.json(
{ code: 500, error: 'Failed to fetch current question', message: error.message },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,64 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db/index';
/**
* Get current question info (including random swap info)
*/
export async function GET(request, { params }) {
const { projectId, taskId } = params;
try {
if (!projectId || !taskId) {
return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 });
}
// Fetch task
const task = await db.task.findUnique({
where: { id: taskId }
});
if (!task || task.taskType !== 'blind-test') {
return NextResponse.json({ error: 'Task not found' }, { status: 404 });
}
// Parse task detail
const detail = JSON.parse(task.detail || '{}');
// Support both evalDatasetIds and questionIds
const questionIds = detail.questionIds || detail.evalDatasetIds || [];
const currentIndex = detail.currentIndex || 0;
// Check if task is completed
if (questionIds.length === 0 || currentIndex >= questionIds.length) {
return NextResponse.json({
completed: true,
currentIndex,
totalQuestions: questionIds.length
});
}
// Fetch current question
const currentQuestionId = questionIds[currentIndex];
const currentQuestion = await db.evalDatasets.findUnique({
where: { id: currentQuestionId }
});
if (!currentQuestion) {
return NextResponse.json({ error: 'Question not found' }, { status: 404 });
}
// Randomly decide whether to swap (core blind-test behavior)
const isSwapped = Math.random() > 0.5;
return NextResponse.json({
questionId: currentQuestion.id,
question: currentQuestion.question,
answer: currentQuestion.correctAnswer || '',
questionIndex: currentIndex + 1,
totalQuestions: questionIds.length,
isSwapped
});
} catch (error) {
console.error('Failed to fetch question info:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}

View File

@@ -0,0 +1,190 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db/index';
/**
* Get blind-test task details
* Results are fetched from EvalResults table
*/
export async function GET(request, { params }) {
try {
const { projectId, taskId } = params;
const task = await db.task.findFirst({
where: {
id: taskId,
projectId,
taskType: 'blind-test'
}
});
if (!task) {
return NextResponse.json({ code: 404, error: 'Task not found' }, { status: 404 });
}
let detail = {};
let modelInfo = {};
try {
detail = task.detail ? JSON.parse(task.detail) : {};
modelInfo = task.modelInfo ? JSON.parse(task.modelInfo) : {};
} catch (e) {
console.error('Failed to parse task detail:', e);
}
// Fetch all related evaluation questions
const evalDatasetIds = detail.evalDatasetIds || [];
const evalDatasets = await db.evalDatasets.findMany({
where: {
id: { in: evalDatasetIds }
},
select: {
id: true,
question: true,
questionType: true,
correctAnswer: true,
tags: true
}
});
// Sort by evalDatasetIds order
const orderedDatasets = evalDatasetIds.map(id => evalDatasets.find(d => d.id === id)).filter(Boolean);
// Fetch results from EvalResults table
const evalResults = await db.evalResults.findMany({
where: { taskId },
orderBy: { createAt: 'asc' }
});
// Parse results into the format expected by frontend
const results = evalResults.map(r => {
let modelAnswer = {};
let judgeData = {};
try {
modelAnswer = JSON.parse(r.modelAnswer || '{}');
judgeData = JSON.parse(r.judgeResponse || '{}');
} catch (e) {
// Ignore parse errors
}
return {
questionId: r.evalDatasetId,
vote: judgeData.vote,
isSwapped: judgeData.isSwapped,
modelAScore: judgeData.modelAScore || 0,
modelBScore: judgeData.modelBScore || 0,
leftAnswer: modelAnswer.leftAnswer || '',
rightAnswer: modelAnswer.rightAnswer || '',
timestamp: r.createAt
};
});
return NextResponse.json({
code: 0,
data: {
...task,
detail: {
...detail,
results // Include results from EvalResults table
},
modelInfo,
evalDatasets: orderedDatasets
}
});
} catch (error) {
console.error('Failed to fetch blind-test task details:', error);
return NextResponse.json(
{ code: 500, error: 'Failed to fetch blind-test task details', message: error.message },
{ status: 500 }
);
}
}
/**
* Update blind-test task (interrupt/stop)
*/
export async function PUT(request, { params }) {
try {
const { projectId, taskId } = params;
const { action } = await request.json();
const task = await db.task.findFirst({
where: {
id: taskId,
projectId,
taskType: 'blind-test'
}
});
if (!task) {
return NextResponse.json({ code: 404, error: 'Task not found' }, { status: 404 });
}
if (action === 'interrupt') {
if (task.status !== 0) {
return NextResponse.json({ code: 400, error: 'Only running tasks can be interrupted' }, { status: 400 });
}
const updatedTask = await db.task.update({
where: { id: taskId },
data: {
status: 3, // Interrupted
endTime: new Date()
}
});
return NextResponse.json({
code: 0,
data: updatedTask,
message: 'Task interrupted'
});
}
return NextResponse.json({ code: 400, error: 'Unknown action' }, { status: 400 });
} catch (error) {
console.error('Failed to update blind-test task:', error);
return NextResponse.json(
{ code: 500, error: 'Failed to update blind-test task', message: error.message },
{ status: 500 }
);
}
}
/**
* Delete blind-test task and its results
*/
export async function DELETE(request, { params }) {
try {
const { projectId, taskId } = params;
const task = await db.task.findFirst({
where: {
id: taskId,
projectId,
taskType: 'blind-test'
}
});
if (!task) {
return NextResponse.json({ code: 404, error: 'Task not found' }, { status: 404 });
}
// Delete related EvalResults first
await db.evalResults.deleteMany({
where: { taskId }
});
// Then delete the task
await db.task.delete({
where: { id: taskId }
});
return NextResponse.json({
code: 0,
message: 'Task deleted'
});
} catch (error) {
console.error('Failed to delete blind-test task:', error);
return NextResponse.json(
{ code: 500, error: 'Failed to delete blind-test task', message: error.message },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,92 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db/index';
import LLMClient from '@/lib/llm/core/index';
import { getModelConfigById } from '@/lib/db/model-config';
/**
* Stream answer for a specified model
* Query param: model=A or model=B
*/
export async function GET(request, { params }) {
const { projectId, taskId } = params;
const { searchParams } = new URL(request.url);
const modelType = searchParams.get('model'); // 'A' or 'B'
try {
if (!projectId || !taskId) {
return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 });
}
if (!modelType || !['A', 'B'].includes(modelType)) {
return NextResponse.json({ error: 'Model type must be specified (A or B)' }, { status: 400 });
}
// Fetch task
const task = await db.task.findUnique({
where: { id: taskId }
});
if (!task || task.taskType !== 'blind-test') {
return NextResponse.json({ error: 'Task not found' }, { status: 404 });
}
// Parse task detail
const detail = JSON.parse(task.detail || '{}');
const modelInfo = JSON.parse(task.modelInfo || '{}');
// Support both evalDatasetIds and questionIds
const questionIds = detail.questionIds || detail.evalDatasetIds || [];
const currentIndex = detail.currentIndex || 0;
// Check if task is completed
if (questionIds.length === 0 || currentIndex >= questionIds.length) {
return NextResponse.json({ completed: true });
}
// Fetch current question
const currentQuestionId = questionIds[currentIndex];
const currentQuestion = await db.evalDatasets.findUnique({
where: { id: currentQuestionId }
});
if (!currentQuestion) {
return NextResponse.json({ error: 'Question not found' }, { status: 404 });
}
// Resolve model config based on modelType
const modelConfigKey = modelType === 'A' ? 'modelA' : 'modelB';
const modelConfig = await getModelConfigById(modelInfo[modelConfigKey].id);
if (!modelConfig) {
return NextResponse.json({ error: 'Model configuration not found' }, { status: 400 });
}
// Prepare messages
const messages = [
{
role: 'system',
content: "You are a helpful assistant. Provide detailed and accurate answers to the user's question."
},
{ role: 'user', content: currentQuestion.question }
];
// Create LLM client
const client = new LLMClient({
projectId,
...modelConfig
});
// Call streaming API and return response directly
const response = await client.chatStreamAPI(messages);
return new Response(response.body, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Cache-Control': 'no-cache',
Connection: 'keep-alive'
}
});
} catch (error) {
console.error(`Model ${modelType} streaming call failed:`, error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}

View File

@@ -0,0 +1,213 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db/index';
import LLMClient from '@/lib/llm/core/index';
import { getModelConfigById } from '@/lib/db/model-config';
/**
* Stream answers from two models for the current question
*/
export async function GET(request, { params }) {
const { projectId, taskId } = params;
try {
if (!projectId || !taskId) {
return NextResponse.json({ error: 'Missing required parameters' }, { status: 400 });
}
// Fetch task
const task = await db.task.findUnique({
where: { id: taskId }
});
if (!task || task.taskType !== 'blind-test') {
return NextResponse.json({ error: 'Task not found' }, { status: 404 });
}
// Parse task detail
const detail = JSON.parse(task.detail || '{}');
const modelInfo = JSON.parse(task.modelInfo || '{}');
const { questionIds = [], currentIndex = 0 } = detail;
// Check if task is completed
if (currentIndex >= questionIds.length) {
return NextResponse.json({ completed: true });
}
// Fetch current question
const currentQuestionId = questionIds[currentIndex];
const currentQuestion = await db.evalDatasets.findUnique({
where: { id: currentQuestionId }
});
if (!currentQuestion) {
return NextResponse.json({ error: 'Question not found' }, { status: 404 });
}
// Fetch model configs
const [modelConfigA, modelConfigB] = await Promise.all([
getModelConfigById(modelInfo.modelA.providerId),
getModelConfigById(modelInfo.modelB.providerId)
]);
if (!modelConfigA || !modelConfigB) {
return NextResponse.json({ error: 'Model configuration not found' }, { status: 400 });
}
// Randomly swap positions (core blind-test behavior)
const isSwapped = Math.random() > 0.5;
// Create streaming response
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
try {
// Send init message
controller.enqueue(
encoder.encode(
JSON.stringify({
type: 'init',
question: currentQuestion.question,
questionId: currentQuestion.id,
questionIndex: currentIndex + 1,
totalQuestions: questionIds.length,
isSwapped
}) + '\n'
)
);
// Prepare messages
const messages = [
{
role: 'system',
content: "You are a helpful assistant. Provide detailed and accurate answers to the user's question."
},
{ role: 'user', content: currentQuestion.question }
];
// Create LLM clients
const clientA = new LLMClient({
projectId,
...modelConfigA
});
const clientB = new LLMClient({
projectId,
...modelConfigB
});
let answerA = '';
let answerB = '';
const startTime = Date.now();
// Call both models in parallel (streaming)
await Promise.all([
(async () => {
try {
const response = await clientA.chatStreamAPI(messages);
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
answerA += chunk;
// Send chunk update
controller.enqueue(
encoder.encode(
JSON.stringify({
type: 'chunk',
model: isSwapped ? 'B' : 'A',
content: chunk
}) + '\n'
)
);
}
} catch (err) {
console.error('Model A call failed:', err);
controller.enqueue(
encoder.encode(
JSON.stringify({
type: 'error',
model: isSwapped ? 'B' : 'A',
error: err.message
}) + '\n'
)
);
}
})(),
(async () => {
try {
const response = await clientB.chatStreamAPI(messages);
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value, { stream: true });
answerB += chunk;
// Send chunk update
controller.enqueue(
encoder.encode(
JSON.stringify({
type: 'chunk',
model: isSwapped ? 'A' : 'B',
content: chunk
}) + '\n'
)
);
}
} catch (err) {
console.error('Model B call failed:', err);
controller.enqueue(
encoder.encode(
JSON.stringify({
type: 'error',
model: isSwapped ? 'A' : 'B',
error: err.message
}) + '\n'
)
);
}
})()
]);
const duration = Date.now() - startTime;
// Send done message
controller.enqueue(
encoder.encode(
JSON.stringify({
type: 'done',
duration,
answerA: isSwapped ? answerB : answerA,
answerB: isSwapped ? answerA : answerB
}) + '\n'
)
);
controller.close();
} catch (error) {
console.error('Streaming handler failed:', error);
controller.error(error);
}
}
});
return new Response(stream, {
headers: {
'Content-Type': 'text/plain; charset=utf-8',
'Cache-Control': 'no-cache',
Connection: 'keep-alive'
}
});
} catch (error) {
console.error('API error:', error);
return NextResponse.json({ error: error.message }, { status: 500 });
}
}

View File

@@ -0,0 +1,154 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db/index';
/**
* Submit vote result
* vote: 'left' | 'right' | 'both_good' | 'both_bad'
* Results are stored in EvalResults table
*/
export async function POST(request, { params }) {
try {
const { projectId, taskId } = params;
const { vote, questionId, isSwapped, leftAnswer, rightAnswer } = await request.json();
// Validate vote option
const validVotes = ['left', 'right', 'both_good', 'both_bad'];
if (!validVotes.includes(vote)) {
return NextResponse.json({ code: 400, error: 'Invalid vote option' }, { status: 400 });
}
if (!questionId) {
return NextResponse.json({ code: 400, error: 'Question ID is required' }, { status: 400 });
}
const task = await db.task.findFirst({
where: {
id: taskId,
projectId,
taskType: 'blind-test'
}
});
if (!task) {
return NextResponse.json({ code: 404, error: 'Task not found' }, { status: 404 });
}
if (task.status !== 0) {
return NextResponse.json({ code: 400, error: 'Task has ended' }, { status: 400 });
}
// Parse task details
let detail = {};
try {
detail = task.detail ? JSON.parse(task.detail) : {};
} catch (e) {
console.error('Failed to parse task detail:', e);
}
// Calculate scores
// isSwapped: true means left is model B and right is model A
// isSwapped: false means left is model A and right is model B
let modelAScore = 0;
let modelBScore = 0;
if (vote === 'left') {
if (isSwapped) {
modelBScore = 1; // Left is B
} else {
modelAScore = 1; // Left is A
}
} else if (vote === 'right') {
if (isSwapped) {
modelAScore = 1; // Right is A
} else {
modelBScore = 1; // Right is B
}
} else if (vote === 'both_good') {
modelAScore = 0.5;
modelBScore = 0.5;
}
// both_bad: both scores remain 0
// Store result in EvalResults table
const evalResult = await db.evalResults.create({
data: {
projectId,
taskId,
evalDatasetId: questionId,
modelAnswer: JSON.stringify({
leftAnswer: leftAnswer || '',
rightAnswer: rightAnswer || ''
}),
score: modelAScore, // Store modelA score for sorting/aggregation
isCorrect: false, // Not applicable for blind-test
judgeResponse: JSON.stringify({
vote,
isSwapped,
modelAScore,
modelBScore
}),
duration: 0,
status: 0
}
});
// Update task progress
const evalDatasetIds = detail.evalDatasetIds || [];
const newCurrentIndex = (detail.currentIndex || 0) + 1;
const isCompleted = newCurrentIndex >= evalDatasetIds.length;
const updatedDetail = {
...detail,
currentIndex: newCurrentIndex
};
await db.task.update({
where: { id: taskId },
data: {
detail: JSON.stringify(updatedDetail),
completedCount: newCurrentIndex,
status: isCompleted ? 1 : 0, // 1-completed, 0-running
endTime: isCompleted ? new Date() : null
}
});
// Calculate current total scores from EvalResults
const allResults = await db.evalResults.findMany({
where: { taskId },
select: { judgeResponse: true }
});
let totalModelAScore = 0;
let totalModelBScore = 0;
for (const r of allResults) {
try {
const judge = JSON.parse(r.judgeResponse || '{}');
totalModelAScore += judge.modelAScore || 0;
totalModelBScore += judge.modelBScore || 0;
} catch (e) {
// Ignore parse errors
}
}
return NextResponse.json({
code: 0,
data: {
success: true,
isCompleted,
currentIndex: newCurrentIndex,
totalCount: evalDatasetIds.length,
scores: {
modelA: totalModelAScore,
modelB: totalModelBScore
}
},
message: isCompleted ? 'Blind-test task completed' : 'Vote recorded'
});
} catch (error) {
console.error('Failed to submit vote result:', error);
return NextResponse.json(
{ code: 500, error: 'Failed to submit vote result', message: error.message },
{ status: 500 }
);
}
}

View File

@@ -0,0 +1,226 @@
import { NextResponse } from 'next/server';
import { db } from '@/lib/db/index';
/**
* Get all blind-test tasks for a project
*/
export async function GET(request, { params }) {
try {
const { projectId } = params;
const { searchParams } = new URL(request.url);
const page = parseInt(searchParams.get('page') || '1');
const pageSize = parseInt(searchParams.get('pageSize') || '20');
if (!projectId) {
return NextResponse.json({ error: 'Project ID is required' }, { status: 400 });
}
const skip = (page - 1) * pageSize;
// Fetch task list and total count
const [tasks, total] = await Promise.all([
db.task.findMany({
where: {
projectId,
taskType: 'blind-test'
},
orderBy: { createAt: 'desc' },
skip,
take: pageSize
}),
db.task.count({
where: {
projectId,
taskType: 'blind-test'
}
})
]);
// Fetch evaluation results for all tasks to calculate scores
const taskIds = tasks.map(t => t.id);
const allEvalResults = await db.evalResults.findMany({
where: { taskId: { in: taskIds } },
select: {
taskId: true,
judgeResponse: true
}
});
// Group results by taskId and calculate scores
const taskScores = {};
for (const result of allEvalResults) {
if (!taskScores[result.taskId]) {
taskScores[result.taskId] = { modelAScore: 0, modelBScore: 0 };
}
try {
const judge = JSON.parse(result.judgeResponse || '{}');
taskScores[result.taskId].modelAScore += judge.modelAScore || 0;
taskScores[result.taskId].modelBScore += judge.modelBScore || 0;
} catch (e) {
// Ignore parse errors
}
}
// Parse task detail fields and attach scores
const tasksWithDetails = tasks.map(task => {
let detail = {};
let modelInfo = {};
try {
detail = task.detail ? JSON.parse(task.detail) : {};
modelInfo = task.modelInfo ? JSON.parse(task.modelInfo) : {};
} catch (e) {
console.error('Failed to parse task detail:', e);
}
// Attach calculated scores as results array
const scores = taskScores[task.id] || { modelAScore: 0, modelBScore: 0 };
const results = [
{
modelAScore: scores.modelAScore,
modelBScore: scores.modelBScore
}
];
return {
...task,
detail: {
...detail,
results // Attach results for display in task card
},
modelInfo
};
});
return NextResponse.json({
code: 0,
data: {
items: tasksWithDetails,
total,
page,
pageSize,
totalPages: Math.ceil(total / pageSize)
}
});
} catch (error) {
console.error('Failed to fetch blind-test task list:', error);
return NextResponse.json(
{ code: 500, error: 'Failed to fetch blind-test task list', message: error.message },
{ status: 500 }
);
}
}
/**
* Create a blind-test task
*/
export async function POST(request, { params }) {
try {
const { projectId } = params;
const data = await request.json();
const { modelA, modelB, evalDatasetIds, language = 'zh-CN' } = data;
if (!modelA || !modelA.modelId || !modelA.providerId) {
return NextResponse.json({ code: 400, error: 'Please select model A' }, { status: 400 });
}
if (!modelB || !modelB.modelId || !modelB.providerId) {
return NextResponse.json({ code: 400, error: 'Please select model B' }, { status: 400 });
}
if (modelA.modelId === modelB.modelId && modelA.providerId === modelB.providerId) {
return NextResponse.json({ code: 400, error: 'The two models must be different' }, { status: 400 });
}
if (!evalDatasetIds || evalDatasetIds.length === 0) {
return NextResponse.json({ code: 400, error: 'Please select questions to evaluate' }, { status: 400 });
}
const evalDatasets = await db.evalDatasets.findMany({
where: {
id: { in: evalDatasetIds },
projectId
},
select: { id: true, questionType: true }
});
const invalidQuestions = evalDatasets.filter(
q => q.questionType !== 'short_answer' && q.questionType !== 'open_ended'
);
if (invalidQuestions.length > 0) {
return NextResponse.json(
{
code: 400,
error: 'Blind-test tasks only support short-answer and open-ended questions'
},
{ status: 400 }
);
}
// Fetch model config info
const [modelConfigA, modelConfigB] = await Promise.all([
db.modelConfig.findFirst({
where: { projectId, providerId: modelA.providerId, modelId: modelA.modelId }
}),
db.modelConfig.findFirst({
where: { projectId, providerId: modelB.providerId, modelId: modelB.modelId }
})
]);
// Build model info (two models)
const modelInfo = {
modelA: {
id: modelConfigA?.id,
modelId: modelA.modelId,
modelName: modelConfigA?.modelName || modelA.modelId,
providerId: modelA.providerId,
providerName: modelConfigA?.providerName || modelA.providerId
},
modelB: {
id: modelConfigB?.id,
modelId: modelB.modelId,
modelName: modelConfigB?.modelName || modelB.modelId,
providerId: modelB.providerId,
providerName: modelConfigB?.providerName || modelB.providerId
}
};
// Build task detail (only store evalDatasetIds and currentIndex)
const taskDetail = {
evalDatasetIds,
currentIndex: 0 // Current question index
};
// Create task
const newTask = await db.task.create({
data: {
projectId,
taskType: 'blind-test',
status: 0, // Running
modelInfo: JSON.stringify(modelInfo),
language,
detail: JSON.stringify(taskDetail),
totalCount: evalDatasetIds.length,
completedCount: 0,
note: ''
}
});
return NextResponse.json({
code: 0,
data: {
...newTask,
detail: taskDetail,
modelInfo
},
message: 'Blind-test task created'
});
} catch (error) {
console.error('Failed to create blind-test task:', error);
return NextResponse.json(
{ code: 500, error: 'Failed to create blind-test task', message: error.message },
{ status: 500 }
);
}
}