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,94 @@
'use client';
import { useState, useEffect, useCallback } from 'react';
/**
* 评估任务详情 Hook
*/
export default function useEvalTaskDetail(projectId, taskId) {
const [task, setTask] = useState(null);
const [results, setResults] = useState([]);
const [stats, setStats] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
// 分页和筛选状态
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [filterType, setFilterType] = useState(null);
const [filterCorrect, setFilterCorrect] = useState(null); // null: all, true: correct, false: incorrect
const [total, setTotal] = useState(0);
// 加载任务详情
const loadData = useCallback(async () => {
if (!projectId || !taskId) return;
try {
setLoading(true);
setError('');
// 构建查询参数
const params = new URLSearchParams({
page: page.toString(),
pageSize: pageSize.toString()
});
if (filterType) {
params.append('type', filterType);
}
if (filterCorrect !== null) {
params.append('isCorrect', filterCorrect.toString());
}
const response = await fetch(`/api/projects/${projectId}/eval-tasks/${taskId}?${params.toString()}`);
const result = await response.json();
if (result.code === 0) {
setTask(result.data.task);
setResults(result.data.results || []);
setTotal(result.data.total || 0);
setStats(result.data.stats);
} else {
setError(result.error || '加载失败');
}
} catch (err) {
console.error('加载任务详情失败:', err);
setError('加载失败');
} finally {
setLoading(false);
}
}, [projectId, taskId, page, pageSize, filterType, filterCorrect]);
// 初始加载
useEffect(() => {
loadData();
}, [loadData]);
// 自动刷新进行中的任务 (仅在第一页且无筛选时刷新,避免干扰用户查看历史记录)
useEffect(() => {
if (task?.status !== 0 || page !== 1 || filterType || filterCorrect !== null) return;
const interval = setInterval(loadData, 3000);
return () => clearInterval(interval);
}, [task?.status, page, filterType, filterCorrect, loadData]);
return {
task,
results,
stats,
total,
page,
setPage,
pageSize,
setPageSize,
filterType,
setFilterType,
filterCorrect,
setFilterCorrect,
loading,
error,
setError,
loadData
};
}

View File

@@ -0,0 +1,187 @@
'use client';
import { useState, useEffect } from 'react';
import { getDefaultScoreAnchors } from '@/lib/llm/prompts/llmJudge';
export function useEvalTaskForm(projectId, open) {
const [models, setModels] = useState([]);
const [selectedModels, setSelectedModels] = useState([]);
const [judgeModel, setJudgeModel] = useState('');
const [evalDatasets, setEvalDatasets] = useState([]);
const [availableTags, setAvailableTags] = useState([]);
// 筛选条件
const [questionTypes, setQuestionTypes] = useState([]);
const [selectedTags, setSelectedTags] = useState([]);
const [searchKeyword, setSearchKeyword] = useState('');
const [questionCount, setQuestionCount] = useState(0);
// 后端统计 & 采样结果
const [filteredTotal, setFilteredTotal] = useState(0);
const [sampledIds, setSampledIds] = useState([]);
const [hasSubjectiveQuestions, setHasSubjectiveQuestions] = useState(false);
// 主观题类型统计(用于确定显示哪个评分规则表单)
const [hasShortAnswer, setHasShortAnswer] = useState(false);
const [hasOpenEnded, setHasOpenEnded] = useState(false);
// 自定义评分规则
const [shortAnswerScoreAnchors, setShortAnswerScoreAnchors] = useState([]);
const [openEndedScoreAnchors, setOpenEndedScoreAnchors] = useState([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');
// 加载数据
useEffect(() => {
if (open && projectId) {
loadModels();
loadEvalDatasets();
}
}, [open, projectId]);
// 当筛选条件变化时,调用后端统计数量
useEffect(() => {
if (!open || !projectId) return;
const controller = new AbortController();
const fetchCount = async () => {
try {
const params = new URLSearchParams();
if (questionTypes.length > 0) {
questionTypes.forEach(t => params.append('questionTypes', t));
}
if (searchKeyword.trim()) {
params.append('keyword', searchKeyword.trim());
}
if (selectedTags.length > 0) {
selectedTags.forEach(tag => params.append('tags', tag));
}
const response = await fetch(`/api/projects/${projectId}/eval-datasets/count?${params.toString()}`, {
signal: controller.signal
});
if (response.ok) {
const result = await response.json();
const total = result?.data?.total ?? 0;
const hasSubjective = result?.data?.hasSubjective ?? false;
const hasShort = result?.data?.hasShortAnswer ?? false;
const hasOpen = result?.data?.hasOpenEnded ?? false;
setFilteredTotal(total);
setHasSubjectiveQuestions(hasSubjective);
setHasShortAnswer(hasShort);
setHasOpenEnded(hasOpen);
}
} catch (err) {
if (err.name !== 'AbortError') {
console.error('加载评估题目数量失败:', err);
}
}
};
fetchCount();
return () => {
controller.abort();
};
}, [open, projectId, questionTypes, selectedTags, searchKeyword]);
const loadModels = async () => {
try {
const response = await fetch(`/api/projects/${projectId}/model-config`);
if (response.ok) {
const result = await response.json();
const modelList = result?.data || [];
const availableModels = modelList.filter(m => m.apiKey && m.apiKey.trim() !== '' && m.status === 1);
setModels(availableModels);
}
} catch (err) {
console.error('加载模型列表失败:', err);
setModels([]);
}
};
const loadEvalDatasets = async () => {
try {
setLoading(true);
// 这里只需要拿到全部可用标签和题型分布,可以复用已有列表接口或标签接口
const response = await fetch(`/api/projects/${projectId}/eval-datasets?includeStats=true&page=1&pageSize=20`);
if (response.ok) {
const data = await response.json();
const stats = data.stats || {};
const byTag = stats.byTag || {};
const tags = Object.keys(byTag);
setAvailableTags(tags.sort());
// 用部分数据来判断是否存在主观题(类型统计更准确)
const byType = stats.byType || {};
const mockDatasets = Object.entries(byType).map(([type]) => ({ questionType: type }));
setEvalDatasets(mockDatasets);
}
} catch (err) {
console.error('加载评估题目失败:', err);
} finally {
setLoading(false);
}
};
const resetFilters = () => {
setQuestionTypes([]);
setSelectedTags([]);
setSearchKeyword('');
setQuestionCount(0);
setFilteredTotal(0);
setSampledIds([]);
setHasShortAnswer(false);
setHasOpenEnded(false);
};
// 初始化评分规则(根据语言环境)
const initScoreAnchors = (language = 'zh-CN') => {
setShortAnswerScoreAnchors(getDefaultScoreAnchors('short_answer', language));
setOpenEndedScoreAnchors(getDefaultScoreAnchors('open_ended', language));
};
const resetForm = () => {
setSelectedModels([]);
setJudgeModel('');
resetFilters();
setError('');
setShortAnswerScoreAnchors([]);
setOpenEndedScoreAnchors([]);
};
return {
models,
selectedModels,
setSelectedModels,
judgeModel,
setJudgeModel,
evalDatasets,
availableTags,
questionTypes,
setQuestionTypes,
selectedTags,
setSelectedTags,
searchKeyword,
setSearchKeyword,
questionCount,
setQuestionCount,
filteredTotal,
sampledIds,
hasSubjectiveQuestions,
hasShortAnswer,
hasOpenEnded,
shortAnswerScoreAnchors,
setShortAnswerScoreAnchors,
openEndedScoreAnchors,
setOpenEndedScoreAnchors,
initScoreAnchors,
loading,
error,
setError,
setSampledIds,
resetFilters,
resetForm
};
}

View File

@@ -0,0 +1,149 @@
'use client';
import { useState, useEffect, useCallback } from 'react';
/**
* 评估任务列表 Hook
*/
export default function useEvalTasks(projectId) {
const [tasks, setTasks] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState('');
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(12);
const [total, setTotal] = useState(0);
// 加载任务列表
const loadTasks = useCallback(
async (isRefresh = false) => {
if (!projectId) return;
try {
if (!isRefresh) setLoading(true);
setError('');
const response = await fetch(`/api/projects/${projectId}/eval-tasks?page=${page}&pageSize=${pageSize}`);
const result = await response.json();
if (result.code === 0) {
setTasks(result.data.items || []);
setTotal(result.data.total || 0);
} else {
setError(result.error || '加载失败');
}
} catch (err) {
console.error('加载评估任务失败:', err);
setError('加载失败');
} finally {
if (!isRefresh) setLoading(false);
}
},
[projectId, page, pageSize]
);
// 初始加载和分页变化加载
useEffect(() => {
loadTasks();
}, [loadTasks]);
// 自动刷新进行中的任务
useEffect(() => {
const hasProcessingTasks = tasks.some(t => t.status === 0);
if (!hasProcessingTasks) return;
const interval = setInterval(() => loadTasks(true), 5000);
return () => clearInterval(interval);
}, [tasks, loadTasks]);
// 删除任务
const deleteTask = useCallback(
async taskId => {
try {
const response = await fetch(`/api/projects/${projectId}/eval-tasks/${taskId}`, {
method: 'DELETE'
});
const result = await response.json();
if (result.code === 0) {
loadTasks();
return true;
} else {
setError(result.error || '删除失败');
return false;
}
} catch (err) {
console.error('删除任务失败:', err);
setError('删除失败');
return false;
}
},
[projectId]
);
// 中断任务
const interruptTask = useCallback(
async taskId => {
try {
const response = await fetch(`/api/projects/${projectId}/eval-tasks/${taskId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ action: 'interrupt' })
});
const result = await response.json();
if (result.code === 0) {
loadTasks();
return true;
} else {
setError(result.error || '中断失败');
return false;
}
} catch (err) {
console.error('中断任务失败:', err);
setError('中断失败');
return false;
}
},
[projectId, loadTasks]
);
// 创建任务
const createTasks = useCallback(
async data => {
try {
const response = await fetch(`/api/projects/${projectId}/eval-tasks`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data)
});
const result = await response.json();
if (result.code === 0) {
loadTasks();
return { success: true, data: result.data };
} else {
return { success: false, error: result.error };
}
} catch (err) {
console.error('创建任务失败:', err);
return { success: false, error: '创建失败' };
}
},
[projectId, loadTasks]
);
return {
tasks,
loading,
error,
setError,
loadTasks,
deleteTask,
interruptTask,
createTasks,
page,
setPage,
pageSize,
setPageSize,
total
};
}